You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2020/09/16 06:28:04 UTC
[apisix] branch master updated: feat: add AK/SK(HMAC) auth plugin.
(#2192)
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 <33...@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]