You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2020/09/09 03:26:31 UTC

[GitHub] [apisix] nic-chen opened a new pull request #2192: [WIP] feat: add AK/SK auth plugin

nic-chen opened a new pull request #2192:
URL: https://github.com/apache/apisix/pull/2192


   ### What this PR does / why we need it:
   add AK/SK auth plugin
   
   ### Pre-submission checklist:
   
   * [x] Did you explain what problem does this PR solve? Or what new features have been added?
   * [] Have you added corresponding test cases?
   * [] Have you modified the corresponding document?
   * [x] Is this PR backward compatible?
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488343173



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,152 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## 测试插件
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       I think this typesetting method can be optimized, for example: `签名的计算公式为: XXXX`, then re-line, `signning_string 的计算公式为: XXX`.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488336742



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,151 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. **HTTP Method** : Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. **HTTP URI** : `HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+3. **canonical_query_string** :`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+> The coding steps are as follows:
+
+* Extract the `query` item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+* Split the `query` into several items according to the & separator, each item is in the form of key=value or only key.
+* Encoding each item after disassembly is divided into the following three situations.
+
+    * When the item has only key, the conversion formula is UriEncode(key) + "=".
+    * When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    * After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding canonical_query_string.

Review comment:
       `After XXX` shouldn't be a case, right?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487970495



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end

Review comment:
       The encoding process of these two branches, which is the "key" method and which is the "key=value" method? I don't quite understand here.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487783372



##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: verify: ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "GET" .. "/hello" ..  "" ..

Review comment:
       it's query string, it could be removed, but stay here could may it more clear.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488476189



##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,596 @@
+#
+# 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';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}

Review comment:
       Is this wrong? It should be "algorithm XXX not supported".




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488558126



##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,596 @@
+#
+# 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';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}

Review comment:
       nice catch




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488680308



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,151 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. **HTTP Method** : Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. **HTTP URI** : `HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+3. **canonical_query_string** :`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+> The coding steps are as follows:
+
+* Extract the `query` item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+* Split the `query` into several items according to the & separator, each item is in the form of key=value or only key.
+* Encoding each item after disassembly is divided into the following three situations.
+
+    * When the item has only key, the conversion formula is UriEncode(key) + "=".
+    * When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    * After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding canonical_query_string.

Review comment:
       ok




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488482999



##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,596 @@
+#
+# 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';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body eval
+qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin hmac-auth err: value should match only one schema, but matches none"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}

Review comment:
       Here "X-HMAC-ACCESS-KEY" should be written with a valid value. Otherwise `response_body` is `{"message":"Invalid access key"}`




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486203895



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},

Review comment:
       Maximum and minimum length

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},

Review comment:
       ditto

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0

Review comment:
       I think 5 `min` = 300 sec is a better default value, @nic-chen what do you think?

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+| access_key         |required|Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      |optional|Use as a pair with `access_key`|
+| algorithm    |  optional|Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`,`hmac-sha512` is default.|

Review comment:
       `hmac-sha256` is the default one?

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},

Review comment:
       if they were all `hmac-*`, how about we remove the fixed prefix `hmac-`?
   
   `{"sha1", "sha256", "sha512"}`, it looks simpler.

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0
+        }
+    },
+    dependencies = {

Review comment:
       I think we should use `required` which is easier to understand

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consume_cache

Review comment:
       `create_consume_cache` -> `create_consumer_cache`

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+| access_key         |required|Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      |optional|Use as a pair with `access_key`|
+| algorithm    |  optional|Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`,`hmac-sha512` is default.|
+| clock_skew  |optional|The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`|

Review comment:
       what is the default value? we need to clear it in doc

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+| access_key         |required|Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      |optional|Use as a pair with `access_key`|
+| algorithm    |  optional|Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`,`hmac-sha512` is default.|
+| clock_skew  |optional|The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],

Review comment:
       mini case is better. let us remove `"methods": ["GET"]`

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+| access_key         |required|Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      |optional|Use as a pair with `access_key`|

Review comment:
       `secret_key` should be a `required` option

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consume_cache
+do
+    local consumer_ids = {}
+
+    function create_consume_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then

Review comment:
       `return ok, err` is enough

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consume_cache
+do
+    local consumer_ids = {}
+
+    function create_consume_cache(consumers)

Review comment:
       ditto, we should use `consumer` here

##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+* `access_key`: 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。

Review comment:
       should use `table` way, same as English version

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,158 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+| access_key         |required|Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      |optional|Use as a pair with `access_key`|
+| algorithm    |  optional|Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`,`hmac-sha512` is default.|
+| clock_skew  |optional|The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHA256-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + CanonicalQueryString + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. CanonicalQueryString
+`CanonicalQueryString` is the result of encoding the Query String in the URL (Query String is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+The coding steps are as follows:
+
+    Extract the Query String item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+    Split the Query String into several items according to the & separator, each item is in the form of key=value or only key.
+    Encoding each item after disassembly is divided into the following three situations.
+
+    When the item has only key, the conversion formula is UriEncode(key) + "=".
+    When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding CanonicalQueryString.
+
+#### Use the generated signature to try the request
+
+**Note: ACCESS_KEY, SIGNATURE, ALGORITHM, TIMESTAMP respectively represent the corresponding variables**
+
+* The signature information is put together in the request header `Authorization` field:
+
+```shell
+$ curl http://127.0.0.1:9080/index.html -H 'Authorization: hmac-auth-v1# + ACCESS_KEY + # + base64_encode(SIGNATURE) + # + ALGORITHM + # + TIMESTAMP' -i
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Length: 13175
+...
+Accept-Ranges: bytes
+
+<!DOCTYPE html>
+<html lang="cn">
+...
+```
+
+* The signature information is separately placed in the request header:
+
+```shell
+$ curl http://127.0.0.1:9080/index.html -H 'X-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-HMAC-ALGORITHM: ALGORITHM' -H 'X-HMAC-TIMESTAMP: TIMESTAMP' -H 'X-HMAC-ACCESS-KEY: ACCESS_KEY' -i
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Length: 13175
+...
+Accept-Ranges: bytes
+
+<!DOCTYPE html>
+<html lang="cn">
+```
+
+## Disable Plugin
+
+When you want to disable the `hmac-auth` plugin, it is very simple,
+you can delete the corresponding json configuration in the plugin configuration,
+no need to restart the service, it will take effect immediately:
+
+```shell
+$ curl http://127.0.0.1:2379/v2/keys/apisix/routes/1 -X PUT -d '
+{
+    "methods": ["GET"],

Review comment:
       ditto

##########
File path: t/APISIX.pm
##########
@@ -96,6 +96,18 @@ etcd:
 _EOC_
 }
 
+my $custom_hmac_auth = $ENV{"CUSTOM_HMAC_AUTH"} || "false";

Review comment:
       why we need this? 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486314631



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0
+        }
+    },
+    dependencies = {

Review comment:
       when configuring routes that enable the plugin, it is more convenient to allow empty configuration.
   what do you think ?
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488073128



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)

Review comment:
       Understand, look at other plug-in processing methods different from this.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486208741



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 0

Review comment:
       I think 300 sec is a better default value, @nic-chen what do you think?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487791781



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)

Review comment:
       Is there no need for error handling here? For example "local ok, err = core.schema.check(schema, conf)".




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488064180



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri
+                            .. canonical_query_string .. req_body
+                            .. params.access_key .. params.timestamp
+                            .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = "algorithm " .. params.algorithm .. " not supported"}
+    end
+
+    core.log.info("conf.clock_skew: ", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff: ", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key

Review comment:
       if `conf ` is nil , it should not read `conf. secret_key `




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487669728



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do

Review comment:
       missing one space, should be `k, v`

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)

Review comment:
       we can use `core.table.insert(query_tab , ...)` way, which is simpler
   
   and the `query_tab_size` is useless

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)
+                    query_tab_size = query_tab_size + 1
+                end
+            else
+                query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(param)
+                query_tab_size = query_tab_size + 1
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body, _ = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+    req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return false, nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return false, nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return false, nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}
+    end
+
+    core.log.info("conf.clock_skew:", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff:", diff)
+        if diff > conf.clock_skew then
+          return false, nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature:", request_signature)
+    core.log.info("generated_signature:", generated_signature)
+
+    return request_signature == generated_signature, consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY
+
+    if try_attr(local_conf, "plugin_attr", "hmac-auth") then
+        local attr = local_conf.plugin_attr["hmac-auth"]
+        signature_key = attr.signature_key or signature_key

Review comment:
       this should be a bug. we should not update the variable `signature_key`, it can be used in multiple requests.

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)
+                    query_tab_size = query_tab_size + 1
+                end
+            else
+                query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(param)
+                query_tab_size = query_tab_size + 1
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body, _ = core.request.get_body()

Review comment:
       `local req_body = core.request.get_body()` simpler code style

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)
+                    query_tab_size = query_tab_size + 1
+                end
+            else
+                query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(param)
+                query_tab_size = query_tab_size + 1
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body, _ = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+    req_body .. params.access_key .. params.timestamp .. secret_key

Review comment:
       bad indentation

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)
+                    query_tab_size = query_tab_size + 1
+                end
+            else
+                query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(param)
+                query_tab_size = query_tab_size + 1
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body, _ = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+    req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return false, nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return false, nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return false, nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}
+    end
+
+    core.log.info("conf.clock_skew:", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff:", diff)
+        if diff > conf.clock_skew then
+          return false, nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature:", request_signature)
+    core.log.info("generated_signature:", generated_signature)
+
+    return request_signature == generated_signature, consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY
+
+    if try_attr(local_conf, "plugin_attr", "hmac-auth") then
+        local attr = local_conf.plugin_attr["hmac-auth"]
+        signature_key = attr.signature_key or signature_key
+        algorithm_key = attr.algorithm_key or algorithm_key
+        timestamp_key = attr.timestamp_key or timestamp_key
+        access_key = attr.access_key or access_key
+    end
+
+    local ak = core.request.header(ctx, access_key)
+    local signature = core.request.header(ctx, signature_key)
+    local algorithm = core.request.header(ctx, algorithm_key)
+    local timestamp = core.request.header(ctx, timestamp_key)
+
+    -- get params from header `Authorization`
+    if not ak then
+        local auth_string = core.request.header(ctx, "Authorization")
+        core.log.info("auth_string:", auth_string)
+        local auth_data = ngx_re.split(auth_string, "#")
+        core.log.info("#auth_data:", #auth_data)
+        core.log.info("auth_data:", core.json.delay_encode(auth_data))
+        if #auth_data == 5 and auth_data[1] == "hmac-auth-v1" then
+            ak = auth_data[2]
+            signature = auth_data[3]
+            algorithm = auth_data[4]
+            timestamp = auth_data[5]
+        end
+    end
+
+
+    params.access_key = ak
+    params.algorithm  = algorithm
+    params.signature  = signature
+    params.timestamp  = timestamp or 0
+
+    return params
+end
+
+
+function _M.rewrite(conf, ctx)
+    local params = get_params(ctx)
+    local ok, validated_consumer, err = validate(ctx, params)

Review comment:
       I think `local validated_consumer, err = validate(ctx, params)` is enough




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487712284



##########
File path: t/plugin/custom_hmac_auth.t
##########
@@ -0,0 +1,364 @@
+#
+# 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.
+#
+BEGIN {

Review comment:
       `custom_hmac_auth.t` is only used to test custom headers. main test cases are in `hmac_auth.t`.
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487958081



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,157 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       Two questions:
   
   1. I think the HTTP body is optional.
   2. For the `signning_string`, we should add a space when we concat different values, eg: `HTTP METHOD` + " " + `HTTP URI`, it is more easier for understanding.

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method

Review comment:
       Please update this English version follow this style:
   
   https://github.com/apache/apisix/pull/2192/commits/94b6ea2c421ebee1ae9b00c394d4e0cc73f33171




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488679450



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,157 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       There is another way to do both, that is, we can put the body hash in request header which could be put in the `signning_string`. what do you think ? @membphis 
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488557288



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,151 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. **HTTP Method** : Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. **HTTP URI** : `HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+3. **canonical_query_string** :`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+> The coding steps are as follows:
+
+* Extract the `query` item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+* Split the `query` into several items according to the & separator, each item is in the form of key=value or only key.
+* Encoding each item after disassembly is divided into the following three situations.
+
+    * When the item has only key, the conversion formula is UriEncode(key) + "=".
+    * When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    * After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding canonical_query_string.

Review comment:
       ping @nic-chen 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] moonming merged pull request #2192: feat: add AK/SK(HMAC) auth plugin.

Posted by GitBox <gi...@apache.org>.
moonming merged pull request #2192:
URL: https://github.com/apache/apisix/pull/2192


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486218352



##########
File path: t/APISIX.pm
##########
@@ -96,6 +96,18 @@ etcd:
 _EOC_
 }
 
+my $custom_hmac_auth = $ENV{"CUSTOM_HMAC_AUTH"} || "false";
+if ($custom_hmac_auth eq "true") {
+    $user_yaml_config .= <<_EOC_;
+plugin_attr:
+  hmac-auth:
+    signature_key: "X-APISIX-HMAC-SIGNATURE"

Review comment:
       we can remove `"` here, make is simpler.
   
   eg: `signature_key: X-APISIX-HMAC-SIGNATURE`




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488557091



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri
+                            .. canonical_query_string .. req_body
+                            .. params.access_key .. params.timestamp
+                            .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = "algorithm " .. params.algorithm .. " not supported"}
+    end
+
+    core.log.info("conf.clock_skew: ", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff: ", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature: ", request_signature,
+        " generated_signature: ", generated_signature)
+
+    if request_signature ~= generated_signature then
+        return nil, {message = "Invalid signature"}
+    end
+
+    return consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY

Review comment:
       I think the current code style is fine. Do you have any suggestions? @Firstsawyou




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487691116



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},
+        secret_key = {type = "string", minLength = 1, maxLength = 256},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+            default = "hmac-sha256"
+        },
+        clock_skew = {
+            type = "integer",
+            default = 300
+        }
+    },
+    dependencies = {
+        ["access_key"] = {"secret_key"},
+        ["secret_key"] = {"access_key"},
+        ["algorithm"]  = {"secret_key", "access_key"}
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        t = t[attr]
+        if type(t) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+        local query_tab_size = 1
+
+        for k,v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(vval)
+                    query_tab_size = query_tab_size + 1
+                end
+            else
+                query_tab[query_tab_size] = escape_uri(key) .. "=" .. escape_uri(param)
+                query_tab_size = query_tab_size + 1
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body, _ = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+    req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return false, nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return false, nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return false, nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}
+    end
+
+    core.log.info("conf.clock_skew:", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff:", diff)
+        if diff > conf.clock_skew then
+          return false, nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature:", request_signature)
+    core.log.info("generated_signature:", generated_signature)
+
+    return request_signature == generated_signature, consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY
+
+    if try_attr(local_conf, "plugin_attr", "hmac-auth") then
+        local attr = local_conf.plugin_attr["hmac-auth"]
+        signature_key = attr.signature_key or signature_key
+        algorithm_key = attr.algorithm_key or algorithm_key
+        timestamp_key = attr.timestamp_key or timestamp_key
+        access_key = attr.access_key or access_key
+    end
+
+    local ak = core.request.header(ctx, access_key)
+    local signature = core.request.header(ctx, signature_key)
+    local algorithm = core.request.header(ctx, algorithm_key)
+    local timestamp = core.request.header(ctx, timestamp_key)
+
+    -- get params from header `Authorization`
+    if not ak then
+        local auth_string = core.request.header(ctx, "Authorization")
+        core.log.info("auth_string:", auth_string)
+        local auth_data = ngx_re.split(auth_string, "#")
+        core.log.info("#auth_data:", #auth_data)
+        core.log.info("auth_data:", core.json.delay_encode(auth_data))
+        if #auth_data == 5 and auth_data[1] == "hmac-auth-v1" then
+            ak = auth_data[2]
+            signature = auth_data[3]
+            algorithm = auth_data[4]
+            timestamp = auth_data[5]
+        end
+    end
+
+

Review comment:
       one blank line is needed




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488101612



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end

Review comment:
       Thanks, I understand.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486312169



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},

Review comment:
       I think use prefix `hmac-` is more clear. `hmac-sha1` and `sha1` are different hash algorithms




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488335359



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,151 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. **HTTP Method** : Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. **HTTP URI** : `HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+3. **canonical_query_string** :`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+> The coding steps are as follows:
+
+* Extract the `query` item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+* Split the `query` into several items according to the & separator, each item is in the form of key=value or only key.
+* Encoding each item after disassembly is divided into the following three situations.
+
+    * When the item has only key, the conversion formula is UriEncode(key) + "=".
+    * When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    * After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding canonical_query_string.

Review comment:
       Is "the following three situations" wrong? Shouldn't it be the two situations?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487997453



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri
+                            .. canonical_query_string .. req_body
+                            .. params.access_key .. params.timestamp
+                            .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = "algorithm " .. params.algorithm .. " not supported"}
+    end
+
+    core.log.info("conf.clock_skew: ", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff: ", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key

Review comment:
       @nic-chen  Is there any difference between the above writing method and this `local secret_key = conf.secret_key` writing method? 

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri
+                            .. canonical_query_string .. req_body
+                            .. params.access_key .. params.timestamp
+                            .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = "algorithm " .. params.algorithm .. " not supported"}
+    end
+
+    core.log.info("conf.clock_skew: ", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff: ", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature: ", request_signature,
+        " generated_signature: ", generated_signature)
+
+    if request_signature ~= generated_signature then
+        return nil, {message = "Invalid signature"}
+    end
+
+    return consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY

Review comment:
       Here again define local variables to receive the constants defined above, is it for better coding style?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487665633



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,291 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string"},
+        secret_key = {type = "string"},
+        algorithm = {
+            type = "string",
+            enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},

Review comment:
       ok, got it. Agree with you




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487938697



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. canonical_query_string
+`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+The coding steps are as follows:

Review comment:
       the current way is not easy to read for the user.
   
   ![image](https://user-images.githubusercontent.com/6814606/93094696-d4af9d80-f6d4-11ea-8ddd-9e0623aa8c03.png)
   

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. canonical_query_string
+`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+The coding steps are as follows:

Review comment:
       @nic-chen I updated the Chinese version with a better markdown style, you can update the English version with same way:
   
   https://github.com/apache/apisix/pull/2192/files#diff-843287a1a4423bbbe03ea7c8a39312d0R95

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. canonical_query_string
+`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+The coding steps are as follows:
+
+    Extract the `query` item in the URL, that is, the string "key1 = valve1 & key2 = valve2" after the "?" in the URL.
+    Split the `query` into several items according to the & separator, each item is in the form of key=value or only key.
+    Encoding each item after disassembly is divided into the following three situations.
+
+    When the item has only key, the conversion formula is UriEncode(key) + "=".
+    When the item is in the form of key=value, the conversion formula is in the form of UriEncode(key) + "=" + UriEncode(value). Here value can be an empty string.
+    After converting each item, sort by key in lexicographic order (ASCII code from small to large), and connect them with the & symbol to generate the corresponding canonical_query_string.
+
+#### Use the generated signature to try the request
+
+**Note: ACCESS_KEY, SIGNATURE, ALGORITHM, TIMESTAMP respectively represent the corresponding variables**
+
+* The signature information is put together in the request header `Authorization` field:
+
+```shell
+$ curl http://127.0.0.1:9080/index.html -H 'Authorization: hmac-auth-v1# + ACCESS_KEY + # + base64_encode(SIGNATURE) + # + ALGORITHM + # + TIMESTAMP' -i
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Length: 13175
+...
+Accept-Ranges: bytes
+
+<!DOCTYPE html>
+<html lang="cn">
+...
+```
+
+* The signature information is separately placed in the request header:
+
+```shell
+$ curl http://127.0.0.1:9080/index.html -H 'X-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-HMAC-ALGORITHM: ALGORITHM' -H 'X-HMAC-TIMESTAMP: TIMESTAMP' -H 'X-HMAC-ACCESS-KEY: ACCESS_KEY' -i
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Length: 13175
+...
+Accept-Ranges: bytes
+
+<!DOCTYPE html>
+<html lang="cn">
+```
+
+## Disable Plugin
+
+When you want to disable the `hmac-auth` plugin, it is very simple,
+you can delete the corresponding json configuration in the plugin configuration,
+no need to restart the service, it will take effect immediately:
+
+```shell
+$ curl http://127.0.0.1:2379/v2/keys/apisix/routes/1 -X PUT -d '
+{
+    "uri": "/index.html",
+    "id": 1,

Review comment:
       remove this line, it is useless




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488339941



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,152 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## 测试插件
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       `签名的计算公式为` Is it necessary to add ":" here?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487924251



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)

Review comment:
       the current way is good. the caller will handle the `err` message.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] moonming commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
moonming commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487694974



##########
File path: t/plugin/custom_hmac_auth.t
##########
@@ -0,0 +1,364 @@
+#
+# 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.
+#
+BEGIN {

Review comment:
       are `custom_hmac_auth.t` and `hmac_auth.t` the same?

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,286 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    properties = {
+        access_key = {type = "string", minLength = 1, maxLength = 256},

Review comment:
       need test cases to cover key over 256.

##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + CanonicalQueryString + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. CanonicalQueryString

Review comment:
       `CanonicalQueryString` -> `canonical_query_string`




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] moonming commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
moonming commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488025254



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end

Review comment:
       > The encoding process of these two branches, which is the "key" method and which is the "key=value" method? I don't quite understand here.
   
   please take a look at `ngx_req.get_uri_args` API first.

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do

Review comment:
       is `vval` typo? why not `val` or `v`?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487736639



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil

Review comment:
       `, nil` is useless

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+        req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}

Review comment:
       use `..` to concat new string

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400

Review comment:
       ditto

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: verify: ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "GET" .. "/hello" ..  "" ..
+        "" .. access_key .. timestamp .. secret_key

Review comment:
       bad indentation

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: verify: ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "GET" .. "/hello" ..  "" ..
+        "" .. access_key .. timestamp .. secret_key
+
+        local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string)
+        core.log.info("signature:", ngx_encode_base64(signature))
+        local headers = {}
+        headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-HMAC-TIMESTAMP"] = timestamp
+        headers["X-HMAC-ACCESS-KEY"] = access_key
+
+        local code, body = t.test('/hello',
+            ngx.HTTP_GET,
+            "",
+            nil,
+            headers
+        )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: add consumer with 0 clock skew
+--- 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": "robin",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "my-access-key3",
+                            "secret_key": "my-secret-key3",
+                            "clock_skew": 0
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "robin",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key3",
+                                    "secret_key": "my-secret-key3",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 0
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: verify: invalid signature
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key3
+--- error_code: 401
+--- response_body
+{"message":"Invalid signature"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: add consumer with 1 clock skew
+--- 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": "pony",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "my-access-key2",
+                            "secret_key": "my-secret-key2",
+                            "clock_skew": 1
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "pony",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key2",
+                                    "secret_key": "my-secret-key2",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 1
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: verify: invalid timestamp
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key2"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key2"
+        local signing_string = "GET" .. "/hello" ..  "" ..
+        "" .. access_key .. timestamp .. secret_key
+
+        ngx.sleep(2)
+
+        local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string)
+        core.log.info("signature:", ngx_encode_base64(signature))
+        local headers = {}
+        headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-HMAC-TIMESTAMP"] = timestamp
+        headers["X-HMAC-ACCESS-KEY"] = access_key
+
+        local code, body = t.test('/hello',
+            ngx.HTTP_GET,
+            core.json.encode(data),
+            nil,
+            headers
+        )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- error_code: 401
+--- response_body eval
+qr/\{"message":"Invalid timestamp"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: verify: put ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local data = {cert = ssl_cert, key = ssl_key, sni = "test.com"}

Review comment:
       `ssl_cert `, global vairable? it should be `nil` always

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+        req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}
+    end
+
+    core.log.info("conf.clock_skew:", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff:", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key
+    local request_signature   = ngx_decode_base64(params.signature)
+    local generated_signature = generate_signature(ctx, secret_key, params)
+
+    core.log.info("request_signature: ", request_signature,
+        " generated_signature: ", generated_signature)
+
+    if request_signature ~= generated_signature then
+        return nil, {message = "Invalid signature"}
+    end
+
+    return consumer
+end
+
+local function get_params(ctx)
+    local params = {}
+    local local_conf = core.config.local_conf()
+    local signature_key = SIGNATURE_KEY
+    local algorithm_key = ALGORITHM_KEY
+    local timestamp_key = TIMESTAMP_KEY
+    local access_key = ACCESS_KEY
+
+    if try_attr(local_conf, "plugin_attr", "hmac-auth") then
+        local attr = local_conf.plugin_attr["hmac-auth"]
+        signature_key = attr.signature_key or signature_key
+        algorithm_key = attr.algorithm_key or algorithm_key
+        timestamp_key = attr.timestamp_key or timestamp_key
+        access_key = attr.access_key or access_key
+    end
+
+    local ak = core.request.header(ctx, access_key)
+    local signature = core.request.header(ctx, signature_key)
+    local algorithm = core.request.header(ctx, algorithm_key)
+    local timestamp = core.request.header(ctx, timestamp_key)
+
+    -- get params from header `Authorization`
+    if not ak then
+        local auth_string = core.request.header(ctx, "Authorization")

Review comment:
       it may be "nil", and we need to add a test case to check this case

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..

Review comment:
       please take a look at this style:
   
   ```lua
   local signing_string = request_method .. canonical_uri 
                          .. canonical_query_string .. req_body 
                          .. params.access_key .. params.timestamp 
                          .. secret_key
   ```

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400

Review comment:
       we'd better check the response body

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {

Review comment:
       https://github.com/apache/apisix/pull/2192/files#diff-5628ae74c6204ec3a971a2b2668b9ce5R17-R19
   
   I think we can drop them. That is the default action.

##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local str_fmt    = string.format
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local conf
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        conf = t[attr]
+        if type(conf) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer, nil
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri ..  canonical_query_string ..
+        req_body .. params.access_key .. params.timestamp .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = str_fmt("algorithm %s not supported", params.algorithm)}
+    end
+
+    core.log.info("conf.clock_skew:", conf.clock_skew)

Review comment:
       missing one space after `:`

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: verify: ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "GET" .. "/hello" ..  "" ..

Review comment:
       `.. ""` is useless?

##########
File path: t/plugin/hmac-auth.t
##########
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+BEGIN {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "false"
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with username and plugins
+--- 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": {
+                            "access_key": "my-access-key",
+                            "secret_key": "my-secret-key"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "jack",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key",
+                                    "secret_key": "my-secret-key",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 300
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: add consumer with plugin hmac-auth - missing secret key
+--- 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": {
+                        "hmac-auth": {
+                            "access_key": "user-key"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add consumer with plugin hmac-auth - missing access key
+--- 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": "bar",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "li",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakeyakey",
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: add consumer with plugin hmac-auth - access key exceeds the length limit
+--- 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": "zhang",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "akey",
+                            "secret_key": "skeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskeyskey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: enable 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": {}
+                    },
+                    "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
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: ljlj
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: verify: ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "GET" .. "/hello" ..  "" ..
+        "" .. access_key .. timestamp .. secret_key
+
+        local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string)
+        core.log.info("signature:", ngx_encode_base64(signature))
+        local headers = {}
+        headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-HMAC-TIMESTAMP"] = timestamp
+        headers["X-HMAC-ACCESS-KEY"] = access_key
+
+        local code, body = t.test('/hello',
+            ngx.HTTP_GET,
+            "",
+            nil,
+            headers
+        )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: add consumer with 0 clock skew
+--- 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": "robin",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "my-access-key3",
+                            "secret_key": "my-secret-key3",
+                            "clock_skew": 0
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "robin",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key3",
+                                    "secret_key": "my-secret-key3",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 0
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: verify: invalid signature
+--- request
+GET /hello
+--- more_headers
+X-HMAC-SIGNATURE: asdf
+X-HMAC-ALGORITHM: hmac-sha256
+X-HMAC-TIMESTAMP: 112
+X-HMAC-ACCESS-KEY: my-access-key3
+--- error_code: 401
+--- response_body
+{"message":"Invalid signature"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: add consumer with 1 clock skew
+--- 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": "pony",
+                    "plugins": {
+                        "hmac-auth": {
+                            "access_key": "my-access-key2",
+                            "secret_key": "my-secret-key2",
+                            "clock_skew": 1
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "pony",
+                            "plugins": {
+                                "hmac-auth": {
+                                    "access_key": "my-access-key2",
+                                    "secret_key": "my-secret-key2",
+                                    "algorithm": "hmac-sha256",
+                                    "clock_skew": 1
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: verify: invalid timestamp
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local secret_key = "my-secret-key2"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key2"
+        local signing_string = "GET" .. "/hello" ..  "" ..
+        "" .. access_key .. timestamp .. secret_key
+
+        ngx.sleep(2)
+
+        local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string)
+        core.log.info("signature:", ngx_encode_base64(signature))
+        local headers = {}
+        headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-HMAC-TIMESTAMP"] = timestamp
+        headers["X-HMAC-ACCESS-KEY"] = access_key
+
+        local code, body = t.test('/hello',
+            ngx.HTTP_GET,
+            core.json.encode(data),
+            nil,
+            headers
+        )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- error_code: 401
+--- response_body eval
+qr/\{"message":"Invalid timestamp"\}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: verify: put ok
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local data = {cert = ssl_cert, key = ssl_key, sni = "test.com"}
+        local req_body = core.json.encode(data)
+        req_body = req_body or ""
+
+        local secret_key = "my-secret-key"
+        local timestamp = ngx_time()
+        local access_key = "my-access-key"
+        local signing_string = "PUT" .. "/hello" ..  "" ..
+        req_body .. access_key .. timestamp .. secret_key
+
+        local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string)
+        core.log.info("signature:", ngx_encode_base64(signature))
+        local headers = {}
+        headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-HMAC-TIMESTAMP"] = timestamp
+        headers["X-HMAC-ACCESS-KEY"] = access_key
+
+        local code, body = t.test('/hello',
+            ngx.HTTP_PUT,
+            req_body,
+            nil,
+            headers
+        )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: verify: put ok (pass auth data by header `Authorization`)
+--- config
+location /t {
+    content_by_lua_block {
+        local ngx_time   = ngx.time
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+        local hmac = require("resty.hmac")
+        local ngx_encode_base64 = ngx.encode_base64
+
+        local data = {cert = ssl_cert, key = ssl_key, sni = "test.com"}

Review comment:
       `ssl_cert`? global variable?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488062025



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,157 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       adding `HTTP body` is safer, that a signature just can put or post a same body.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] moonming commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
moonming commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488027011



##########
File path: t/plugin/custom_hmac_auth.t
##########
@@ -0,0 +1,364 @@
+#
+# 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.
+#
+BEGIN {

Review comment:
       got it, you need give `custom_hmac_auth.t` a better name.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486218352



##########
File path: t/APISIX.pm
##########
@@ -96,6 +96,18 @@ etcd:
 _EOC_
 }
 
+my $custom_hmac_auth = $ENV{"CUSTOM_HMAC_AUTH"} || "false";
+if ($custom_hmac_auth eq "true") {
+    $user_yaml_config .= <<_EOC_;
+plugin_attr:
+  hmac-auth:
+    signature_key: "X-APISIX-HMAC-SIGNATURE"

Review comment:
       we can remove `"` here, make it simpler.
   
   eg: `signature_key: X-APISIX-HMAC-SIGNATURE`




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r486218030



##########
File path: t/APISIX.pm
##########
@@ -96,6 +96,18 @@ etcd:
 _EOC_
 }
 
+my $custom_hmac_auth = $ENV{"CUSTOM_HMAC_AUTH"} || "false";

Review comment:
       I got why we need this, that is a good feature.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] nic-chen commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
nic-chen commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488063399



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do

Review comment:
       is a typo here.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488348093



##########
File path: doc/zh-cn/plugins/hmac-auth.md
##########
@@ -0,0 +1,157 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [English](../../plugins/hmac-auth.md)
+
+# 目录
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+
+## 名字
+
+`hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。
+
+添加 HMAC Authentication 到一个 `service` 或 `route`。 然后 `consumer` 将其签名添加到请求头以验证其请求。
+
+## 属性
+
+|属性名         |是否可选 | 默认值 |描述|
+|---------     |--------|-----------|-----------|
+| `access_key` | 必须 | 无 | 不同的 `consumer` 对象应有不同的值,它应当是唯一的。不同 consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。|
+| `secret_key`| 必须 | 无 | 与 `access_key` 配对使用。|
+| `algorithm` | 可选 | hmac-sha256 | 加密算法。目前支持 `hmac-sha1`, `hmac-sha256` 和 `hmac-sha512`。|
+| `clock_skew`| 可选 | 300 | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `timestamp` 进行检查。|
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. 创建 Route 或 Service 对象,并开启 `hmac-auth` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+### 签名生成公式
+
+签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signning_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signning_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signning_string` 的计算公式为: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + access_key + timestamp + secret_key`

Review comment:
       > I think the HTTP body is optional.
   
   this way is better, the request body maybe large.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] membphis commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
membphis commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r487937146



##########
File path: doc/plugins/hmac-auth.md
##########
@@ -0,0 +1,156 @@
+<!--
+#
+# 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.
+#
+-->
+
+- [中文](../zh-cn/plugins/hmac-auth.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+
+## Name
+
+`hmac-auth` is an authentication plugin that need to work with `consumer`. Add HMAC Authentication to a `service` or `route`.
+
+The `consumer` then adds its key to request header to verify its request.
+
+## Attributes
+
+|Name          |Requirement |Default  |Description|
+|---------     |--------|-----------|-----------|
+| access_key         | required | none |Different `consumer` objects should have different values, and it should be unique. If different consumers use the same `access_key`, a request matching exception will occur|
+| secret_key      | required | none |Use as a pair with `access_key`|
+| algorithm    |  optional| hmac-sha256 |Encryption algorithm. support `hmac-sha1`, `hmac-sha256` and `hmac-sha512`|
+| clock_skew  | optional | 300 |The clock skew allowed by the signature in seconds. For example, if the time is allowed to skew by 10 seconds, then it should be set to `10`. especially, `0` means not checking timestamp.|
+
+## How To Enable
+
+1. set a consumer and config the value of the `hmac-auth` option
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "jack",
+    "plugins": {
+        "hmac-auth": {
+            "access_key": "user-key",
+            "secret_key": "my-secret-key",
+            "clock_skew": 10
+        }
+    }
+}'
+```
+
+2. add a Route or add a Service , and enable the `hmac-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {
+        "hmac-auth": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### generate signature:
+The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signning_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `SIGNNING_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `SIGNNING_STRING` is: `signning_string = HTTP Method + HTTP URI + canonical_query_string + HTTP BODY + ACCESS_KEY + TIMESTAMP + SECRET_KEY`
+
+1. HTTP Method
+Refers to the GET, PUT, POST and other request methods defined in the HTTP protocol, and must be in all uppercase.
+2. HTTP URI
+`HTTP URI` requirements must start with "/", those that do not start with "/" need to be added, and the empty path is "/".
+
+3. canonical_query_string
+`canonical_query_string` is the result of encoding the `query` in the URL (`query` is the string "key1 = valve1 & key2 = valve2" after the "?" in the URL).
+
+The coding steps are as follows:

Review comment:
       @nic-chen I updated the Chinese version with a better markdown style, you can update the English version with same way:
   
   https://github.com/apache/apisix/pull/2192/commits/3c7f17d6ad525ec26e9ec7a726a2afbe2f8572b3




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Firstsawyou commented on a change in pull request #2192: feat: add AK/SK auth plugin

Posted by GitBox <gi...@apache.org>.
Firstsawyou commented on a change in pull request #2192:
URL: https://github.com/apache/apisix/pull/2192#discussion_r488095303



##########
File path: apisix/plugins/hmac-auth.lua
##########
@@ -0,0 +1,303 @@
+--
+-- 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.
+--
+local ngx        = ngx
+local type       = type
+local select     = select
+local abs        = math.abs
+local ngx_time   = ngx.time
+local ngx_re     = require("ngx.re")
+local ngx_req    = ngx.req
+local pairs      = pairs
+local ipairs     = ipairs
+local hmac_sha1  = ngx.hmac_sha1
+local escape_uri = ngx.escape_uri
+local core       = require("apisix.core")
+local hmac       = require("resty.hmac")
+local consumer   = require("apisix.consumer")
+local ngx_decode_base64 = ngx.decode_base64
+
+local SIGNATURE_KEY = "X-HMAC-SIGNATURE"
+local ALGORITHM_KEY = "X-HMAC-ALGORITHM"
+local TIMESTAMP_KEY = "X-HMAC-TIMESTAMP"
+local ACCESS_KEY    = "X-HMAC-ACCESS-KEY"
+local plugin_name   = "hmac-auth"
+
+local schema = {
+    type = "object",
+    oneOf = {
+        {
+            title = "work with consumer object",
+            properties = {
+                access_key = {type = "string", minLength = 1, maxLength = 256},
+                secret_key = {type = "string", minLength = 1, maxLength = 256},
+                algorithm = {
+                    type = "string",
+                    enum = {"hmac-sha1", "hmac-sha256", "hmac-sha512"},
+                    default = "hmac-sha256"
+                },
+                clock_skew = {
+                    type = "integer",
+                    default = 300
+                }
+            },
+            required = {"access_key", "secret_key"},
+            additionalProperties = false,
+        },
+        {
+            title = "work with route or service object",
+            properties = {},
+            additionalProperties = false,
+        }
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2530,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+local hmac_funcs = {
+    ["hmac-sha1"] = function(secret_key, message)
+        return hmac_sha1(secret_key, message)
+    end,
+    ["hmac-sha256"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA256):final(message)
+    end,
+    ["hmac-sha512"] = function(secret_key, message)
+        return hmac:new(secret_key, hmac.ALGOS.SHA512):final(message)
+    end,
+}
+
+
+local function try_attr(t, ...)
+    local tbl = t
+    local count = select('#', ...)
+    for i = 1, count do
+        local attr = select(i, ...)
+        tbl = tbl[attr]
+        if type(tbl) ~= "table" then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+local create_consumer_cache
+do
+    local consumer_ids = {}
+
+    function create_consumer_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.access_key] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    return core.schema.check(schema, conf)
+end
+
+
+local function get_consumer(access_key)
+    if not access_key then
+        return nil, {message = "missing access key"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return nil, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consumer_cache, consumer_conf)
+
+    local consumer = consumers[access_key]
+    if not consumer then
+        return nil, {message = "Invalid access key"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    return consumer
+end
+
+
+local function generate_signature(ctx, secret_key, params)
+    local canonical_uri = ctx.var.uri
+    local canonical_query_string = ""
+    local request_method = ngx_req.get_method()
+    local args = ngx_req.get_uri_args()
+
+    if canonical_uri == "" then
+        canonical_uri = "/"
+    end
+
+    if type(args) == "table" then
+        local keys = {}
+        local query_tab = {}
+
+        for k, v in pairs(args) do
+            core.table.insert(keys, k)
+        end
+        core.table.sort(keys)
+
+        for _, key in pairs(keys) do
+            local param = args[key]
+            if type(param) == "table" then
+                for _, vval in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(vval))
+                end
+            else
+                core.table.insert(query_tab, escape_uri(key) .. "=" .. escape_uri(param))
+            end
+        end
+        canonical_query_string = core.table.concat(query_tab, "&")
+    end
+
+    local req_body = core.request.get_body()
+    req_body = req_body or ""
+
+    local signing_string = request_method .. canonical_uri
+                            .. canonical_query_string .. req_body
+                            .. params.access_key .. params.timestamp
+                            .. secret_key
+
+    return hmac_funcs[params.algorithm](secret_key, signing_string)
+end
+
+
+local function validate(ctx, params)
+    if not params.access_key or not params.signature then
+        return nil, {message = "access key or signature missing"}
+    end
+
+    local consumer, err = get_consumer(params.access_key)
+    if err then
+        return nil, err
+    end
+
+    local conf = consumer.auth_conf
+    if conf.algorithm ~= params.algorithm then
+        return nil, {message = "algorithm " .. params.algorithm .. " not supported"}
+    end
+
+    core.log.info("conf.clock_skew: ", conf.clock_skew)
+    if conf.clock_skew and conf.clock_skew > 0 then
+        local diff = abs(ngx_time() - params.timestamp)
+        core.log.info("conf.diff: ", diff)
+        if diff > conf.clock_skew then
+          return nil, {message = "Invalid timestamp"}
+        end
+    end
+
+    local secret_key          = conf and conf.secret_key

Review comment:
       thank you, I know now.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org