This is an automated email from the ASF dual-hosted git repository.

wenming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 84ce7ba  feat: add AK/SK(HMAC) auth plugin. (#2192)
84ce7ba is described below

commit 84ce7ba781affbf93c136ea5d3e2e12460ebe4ed
Author: nic-chen <33000667+nic-c...@users.noreply.github.com>
AuthorDate: Wed Sep 16 14:27:56 2020 +0800

    feat: add AK/SK(HMAC) auth plugin. (#2192)
---
 apisix/core/table.lua          |   1 +
 apisix/plugins/hmac-auth.lua   | 303 +++++++++++++++++++++
 conf/config-default.yaml       |   1 +
 doc/plugins/hmac-auth.md       | 151 +++++++++++
 doc/zh-cn/plugins/hmac-auth.md | 152 +++++++++++
 t/APISIX.pm                    |  12 +
 t/admin/plugins.t              |   2 +-
 t/debug/debug-mode.t           |   1 +
 t/plugin/custom_hmac_auth.t    | 364 +++++++++++++++++++++++++
 t/plugin/hmac-auth.t           | 596 +++++++++++++++++++++++++++++++++++++++++
 10 files changed, 1582 insertions(+), 1 deletion(-)

diff --git a/apisix/core/table.lua b/apisix/core/table.lua
index 5c84164..4ad92ca 100644
--- a/apisix/core/table.lua
+++ b/apisix/core/table.lua
@@ -32,6 +32,7 @@ local _M = {
     nkeys   = nkeys,
     insert  = table.insert,
     concat  = table.concat,
+    sort    = table.sort,
     clone   = require("table.clone"),
     isarray = require("table.isarray"),
 }
diff --git a/apisix/plugins/hmac-auth.lua b/apisix/plugins/hmac-auth.lua
new file mode 100644
index 0000000..ad578c4
--- /dev/null
+++ b/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 _, val in pairs(param) do
+                    core.table.insert(query_tab, escape_uri(key) .. "=" .. 
escape_uri(val))
+                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
+
+    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)
+    core.log.info("signature_key: ", signature_key)
+
+    -- get params from header `Authorization`
+    if not ak then
+        local auth_string = core.request.header(ctx, "Authorization")
+        if not auth_string then
+            return params
+        end
+
+        local auth_data = ngx_re.split(auth_string, "#")
+        core.log.info("auth_string: ", auth_string, " #auth_data: ",
+            #auth_data, " 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 validated_consumer, err = validate(ctx, params)
+    if err then
+        return 401, err
+    end
+
+    if not validated_consumer then
+        return 401, {message = "Invalid signature"}
+    end
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    ctx.consumer = validated_consumer
+    ctx.consumer_id = validated_consumer.consumer_id
+    ctx.consumer_ver = consumer_conf.conf_version
+    core.log.info("hit hmac-auth rewrite")
+end
+
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index f6a4710..6e09012 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -191,6 +191,7 @@ plugins:                          # plugin list
   - proxy-cache
   - proxy-mirror
   - request-id
+  - hmac-auth
 
 stream_plugins:
   - mqtt-proxy
diff --git a/doc/plugins/hmac-auth.md b/doc/plugins/hmac-auth.md
new file mode 100644
index 0000000..af3c671
--- /dev/null
+++ b/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 two 
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:9080/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
diff --git a/doc/zh-cn/plugins/hmac-auth.md b/doc/zh-cn/plugins/hmac-auth.md
new file mode 100644
index 0000000..37305ee
--- /dev/null
+++ b/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`
+
+1. **HTTP Method**:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。
+2. **HTTP URI**:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。
+3. **canonical_query_string**:是对于 URL 中的 query( query 即 URL 中 ? 后面的 
key1=valve1&key2=valve2 字符串)进行编码后的结果。
+
+> canonical_query_string 编码步骤如下:
+
+* 提取 URL 中的 query 项,即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串。
+* 将 query 根据&分隔符拆开成若干项,每一项是 key=value 或者只有 key 的形式。
+* 对拆开后的每一项进行编码处理,分以下两种情况:
+  * 当该项只有 key 时,转换公式为 url_encode(key) + "=" 的形式。
+  * 当该项是 key=value 的形式时,转换公式为 url_encode(key) + "=" + url_encode(value) 的形式。这里 
value 可以是空字符串。
+  * 将每一项转换后,以 key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 
canonical_query_string 。
+
+
+
+### 使用生成好的签名进行请求尝试
+
+**注: ACCESS_KEY,SIGNATURE,ALGORITHM,TIMESTAMP 分别代表对应的变量**
+
+* 签名信息拼一起放到请求头 `Authorization` 字段中:
+
+```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">
+...
+```
+
+* 签名信息分开分别放到请求头:
+
+```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">
+```
+
+
+## 禁用插件
+
+当你想去掉 `hmac-auth` 插件的时候,很简单,在插件的配置中把对应的 `json` 配置删除即可,无须重启服务,即刻生效:
+
+```shell
+$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/index.html",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "39.97.63.215:80": 1
+        }
+    }
+}'
+```
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 7434d43..b7127ed 100644
--- a/t/APISIX.pm
+++ b/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
+    algorithm_key: X-APISIX-HMAC-ALGORITHM
+    timestamp_key: X-APISIX-HMAC-TIMESTAMP
+    access_key: X-APISIX-HMAC-ACCESS-KEY
+_EOC_
+}
+
 
 my $profile = $ENV{"APISIX_PROFILE"};
 
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index d750390..fa69ec6 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -30,7 +30,7 @@ __DATA__
 --- request
 GET /apisix/admin/plugins/list
 --- response_body_like eval
-qr/\["request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin","skywalking"
 [...]
+qr/\["request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin",
 [...]
 --- no_error_log
 [error]
 
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 0b466e6..442cce1 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -56,6 +56,7 @@ loaded plugin and sort by priority: 2900 name: uri-blocker
 loaded plugin and sort by priority: 2800 name: request-validation
 loaded plugin and sort by priority: 2599 name: openid-connect
 loaded plugin and sort by priority: 2555 name: wolf-rbac
+loaded plugin and sort by priority: 2530 name: hmac-auth
 loaded plugin and sort by priority: 2520 name: basic-auth
 loaded plugin and sort by priority: 2510 name: jwt-auth
 loaded plugin and sort by priority: 2500 name: key-auth
diff --git a/t/plugin/custom_hmac_auth.t b/t/plugin/custom_hmac_auth.t
new file mode 100644
index 0000000..f52d13b
--- /dev/null
+++ b/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 {
+    $ENV{"CUSTOM_HMAC_AUTH"} = "true"
+}
+
+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": "jack",
+                    "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": "jack",
+                    "plugins": {
+                        "hmac-auth": {
+                            "secret_key": "skey"
+                        }
+                    }
+                }]])
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: 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 5: verify, missing signature
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: verify: invalid access key
+--- request
+GET /hello
+--- more_headers
+X-APISIX-HMAC-SIGNATURE: asdf
+X-APISIX-HMAC-ALGORITHM: hmac-sha256
+X-APISIX-HMAC-TIMESTAMP: 112
+X-APISIX-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: verify: invalid algorithm
+--- request
+GET /hello
+--- more_headers
+X-APISIX-HMAC-SIGNATURE: asdf
+X-APISIX-HMAC-ALGORITHM: ljlj
+X-APISIX-HMAC-TIMESTAMP: 112
+X-APISIX-HMAC-ACCESS-KEY: sdf
+--- error_code: 401
+--- response_body
+{"message":"Invalid access key"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: verify: invalid timestamp
+--- request
+GET /hello
+--- more_headers
+X-APISIX-HMAC-SIGNATURE: asdf
+X-APISIX-HMAC-ALGORITHM: hmac-sha256
+X-APISIX-HMAC-TIMESTAMP: 112
+X-APISIX-HMAC-ACCESS-KEY: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"Invalid timestamp"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: 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-APISIX-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-APISIX-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-APISIX-HMAC-TIMESTAMP"] = timestamp
+        headers["X-APISIX-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 10: update consumer with 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 11: 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-APISIX-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
+        headers["X-APISIX-HMAC-ALGORITHM"] = "hmac-sha256"
+        headers["X-APISIX-HMAC-TIMESTAMP"] = timestamp
+        headers["X-APISIX-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]
diff --git a/t/plugin/hmac-auth.t b/t/plugin/hmac-auth.t
new file mode 100644
index 0000000..772d22f
--- /dev/null
+++ b/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: my-access-key
+--- error_code: 401
+--- response_body
+{"message":"algorithm ljlj not supported"}
+--- 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"}
+        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 auth_string = "hmac-auth-v1#" .. access_key .. "#" .. 
ngx_encode_base64(signature) .. "#" ..
+        "hmac-sha256#" .. timestamp
+        local headers = {}
+        headers["Authorization"] = auth_string
+
+        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 18: hit route without auth info
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"access key or signature missing"}
+--- no_error_log
+[error]

Reply via email to