This is an automated email from the ASF dual-hosted git repository. spacewander 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 20c670c change(hmac-auth): update the organization of `signing_string` . (#2337) 20c670c is described below commit 20c670ce097af6bbd1bdb150f26faa77d582d61e Author: YuanSheng Wang <membp...@gmail.com> AuthorDate: Sun Oct 4 19:57:57 2020 +0800 change(hmac-auth): update the organization of `signing_string` . (#2337) fix #2336 . ref: https://help.aliyun.com/document_detail/29475.html?spm=a2c4g.11186623.2.15.62c73e789LVg02 https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationConstructingCanonicalizedAmzHeaders --- apisix/plugins/hmac-auth.lua | 20 ++++++++------ doc/plugins/hmac-auth.md | 27 +++++++++++++++---- doc/zh-cn/plugins/hmac-auth.md | 26 ++++++++++++++---- t/plugin/consumer-restriction.t | 50 ++++++++++++++++++++++++++++------- t/plugin/custom_hmac_auth.t | 13 +++++++-- t/plugin/hmac-auth.t | 58 ++++++++++++++++++++++++++++++++--------- 6 files changed, 153 insertions(+), 41 deletions(-) diff --git a/apisix/plugins/hmac-auth.lua b/apisix/plugins/hmac-auth.lua index 0c37ad7..2c9d0ac 100644 --- a/apisix/plugins/hmac-auth.lua +++ b/apisix/plugins/hmac-auth.lua @@ -201,27 +201,31 @@ local function generate_signature(ctx, secret_key, params) canonical_query_string = core.table.concat(query_tab, "&") end - local canonical_headers = {} - core.log.info("all headers: ", core.json.delay_encode(core.request.headers(ctx), true)) + local signing_string_items = { + request_method, + canonical_uri, + canonical_query_string, + params.access_key, + params.date, + } + if params.signed_headers then for _, h in ipairs(params.signed_headers) do local canonical_header = core.request.header(ctx, h) or "" - core.table.insert(canonical_headers, canonical_header) + core.table.insert(signing_string_items, + h .. ":" .. canonical_header) core.log.info("canonical_header name:", core.json.delay_encode(h)) core.log.info("canonical_header value: ", core.json.delay_encode(canonical_header)) end end - local signing_string = request_method .. canonical_uri - .. canonical_query_string - .. params.access_key .. params.date - .. core.table.concat(canonical_headers, "") + local signing_string = core.table.concat(signing_string_items, "\n") - core.log.info("signing_string:", signing_string, + core.log.info("signing_string: ", signing_string, " params.signed_headers:", core.json.delay_encode(params.signed_headers)) diff --git a/doc/plugins/hmac-auth.md b/doc/plugins/hmac-auth.md index a3d31e2..753addd 100644 --- a/doc/plugins/hmac-auth.md +++ b/doc/plugins/hmac-auth.md @@ -20,13 +20,13 @@ - [中文](../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`. @@ -83,7 +83,8 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ## Test Plugin ### generate signature: -The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signing_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `signing_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `signing_STRING` is `signing_string = HTTP Method + HTTP URI + canonical_query_string + access_key + Date + signed_headers_string` + +The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_key, signing_string)`. From the formula, it can be seen that in order to obtain the signature, two parameters, `SECRET_KEY` and `signing_STRING`, are required. Where secret_key is configured by the corresponding consumer, the calculation formula of `signing_STRING` is `signing_string = signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_ [...] 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 "/". @@ -96,7 +97,6 @@ The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_ke * 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. @@ -104,12 +104,29 @@ The calculation formula of the signature is `signature = HMAC-SHAx-HEX(secret_ke > The signed_headers_string generation steps are as follows: * Obtain the headers specified to be added to the calculation from the request header. For details, please refer to the placement of `SIGNED_HEADERS` in the next section `Use the generated signature to make a request attempt`. -* Take out the headers specified by `SIGNED_HEADERS` in order from the request header, and splice them together in order. After splicing, a `signed_headers_string` is generated. +* Take out the headers specified by `SIGNED_HEADERS` in order from the request header, and splice them together in order of `name:value`. After splicing, a `signed_headers_string` is generated. + +```plain +HeaderKey1 + ":" + HeaderValue1 + "\n"\+ +HeaderKey2 + ":" + HeaderValue2 + "\n"\+ +... +HeaderKeyN + ":" + HeaderValueN +``` + +Here is a full example: + +```plain +GET +/hello +your-access-key +Mon, 28 Sep 2020 06:48:57 GMT +x-custom-header:value +``` ### Use the generated signature to try the request **Note: ACCESS_KEY, SIGNATURE, ALGORITHM, DATE, SIGNED_HEADERS respectively represent the corresponding variables** -**Note: SIGNED_HEADERS is the headers specified by the client to join the encryption calculation, multiple separated by semicolons, Example: User-Agent;Accept-Language** +**Note: SIGNED_HEADERS is the headers specified by the client to join the encryption calculation** * The signature information is put together in the request header `Authorization` field: diff --git a/doc/zh-cn/plugins/hmac-auth.md b/doc/zh-cn/plugins/hmac-auth.md index 912003f..8ce726f 100644 --- a/doc/zh-cn/plugins/hmac-auth.md +++ b/doc/zh-cn/plugins/hmac-auth.md @@ -20,13 +20,13 @@ - [English](../../plugins/hmac-auth.md) # 目录 + - [**名字**](#名字) - [**属性**](#属性) - [**如何启用**](#如何启用) - [**测试插件**](#测试插件) - [**禁用插件**](#禁用插件) - ## 名字 `hmac-auth` 是一个认证插件,它需要与 `consumer` 一起配合才能工作。 @@ -43,7 +43,6 @@ | clock_skew | integer | 可选 | 0 | | 签名允许的时间偏移,以秒为单位的计时。比如允许时间偏移 10 秒钟,那么就应设置为 `10`。特别地,`0` 表示不对 `Date` 进行检查。 | | signed_headers | array[string] | 可选 | | | 限制加入加密计算的 headers ,指定后客户端请求只能在此范围内指定 headers ,此项为空时将把所有客户端请求指定的 headers 加入加密计算。如: ["User-Agent", "Accept-Language", "x-custom-a"] | - ## 如何启用 1. 创建一个 consumer 对象,并设置插件 `hmac-auth` 的值。 @@ -85,7 +84,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ### 签名生成公式 -签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signing_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signing_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signing_string` 的计算公式为 `signing_string = HTTP Method + HTTP URI + canonical_query_string + access_key + Date + signed_headers_string` +签名的计算公式为 `signature = HMAC-SHAx-HEX(secret_key, signing_string)`,从公式可以看出,想要获得签名需要得到 `secret_key` 和 `signing_string` 两个参数。其中 `secret_key` 为对应 consumer 所配置的, `signing_string` 的计算公式为 `signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string`。 1. **HTTP Method**:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。 2. **HTTP URI**:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。 @@ -105,13 +104,30 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 > signed_headers_string 生成步骤如下: * 从请求头中获取指定加入计算的 headers ,具体请参考下节 `使用生成好的签名进行请求尝试` 中的 `SIGNED_HEADERS` 放置的位置。 -* 从请求头中按顺序取出 `SIGNED_HEADERS` 指定的 headers ,并按顺序拼接起来,拼接完后就生成了 `signed_headers_string` 。 +* 从请求头中按顺序取出 `SIGNED_HEADERS` 指定的 headers ,并按顺序用`name:value`方式拼接起来,拼接完后就生成了 `signed_headers_string` 。 +```plain +HeaderKey1 + ":" + HeaderValue1 + "\n"\+ +HeaderKey2 + ":" + HeaderValue2 + "\n"\+ +... +HeaderKeyN + ":" + HeaderValueN +``` + +拼接后的示例: + +```plain +GET +/hello + +your-access-key +Mon, 28 Sep 2020 06:48:57 GMT +x-custom-header:value +``` ### 使用生成好的签名进行请求尝试 **注: ACCESS_KEY, SIGNATURE, ALGORITHM, DATE, SIGNED_HEADERS 分别代表对应的变量** -**注: SIGNED_HEADERS 为客户端指定的加入加密计算的 headers ,多个以英文分号分隔如:User-Agent;Accept-Language** +**注: SIGNED_HEADERS 为客户端指定的加入加密计算的 headers** * 签名信息拼一起放到请求头 `Authorization` 字段中: diff --git a/t/plugin/consumer-restriction.t b/t/plugin/consumer-restriction.t index 491de4e..afa7492 100644 --- a/t/plugin/consumer-restriction.t +++ b/t/plugin/consumer-restriction.t @@ -723,8 +723,16 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) @@ -880,8 +888,16 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) @@ -903,7 +919,7 @@ location /t { if code >= 300 then ngx.status = code end - + ngx.say(body) } } @@ -1051,8 +1067,16 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) @@ -1163,8 +1187,16 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) diff --git a/t/plugin/custom_hmac_auth.t b/t/plugin/custom_hmac_auth.t index e02705b..3fc4751 100644 --- a/t/plugin/custom_hmac_auth.t +++ b/t/plugin/custom_hmac_auth.t @@ -244,8 +244,17 @@ location /t { local access_key = "my-access-key" local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) diff --git a/t/plugin/hmac-auth.t b/t/plugin/hmac-auth.t index efdf9f0..fee4598 100644 --- a/t/plugin/hmac-auth.t +++ b/t/plugin/hmac-auth.t @@ -338,8 +338,17 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") + core.log.info("signing_string:", signing_string) local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) @@ -501,7 +510,7 @@ location /t { local access_key = "my-access-key2" local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - + ngx.sleep(2) local signing_string = "GET" .. "/hello" .. "" .. @@ -561,8 +570,17 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "PUT" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "PUT", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") + core.log.info("signing_string:", signing_string) local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature)) @@ -617,19 +635,28 @@ location /t { local custom_header_a = "asld$%dfasf" local custom_header_b = "23879fmsldfk" - local signing_string = "PUT" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a .. custom_header_b + local signing_string = { + "PUT", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") + core.log.info("signing_string:", signing_string) 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#" .. gmt .. "#x-custom-header-a;x-custom-header-b" - + local headers = {} headers["Authorization"] = auth_string headers["x-custom-header-a"] = custom_header_a - headers["x-custom-header-b"] = custom_header_b - + headers["x-custom-header-b"] = custom_header_b + local code, body = t.test('/hello', ngx.HTTP_PUT, req_body, @@ -780,8 +807,15 @@ location /t { local access_key = "my-access-key5" local custom_header_a = "asld$%dfasf" - local signing_string = "GET" .. "/hello" .. "" .. - access_key .. gmt .. custom_header_a + local signing_string = { + "GET", + "/hello", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a + } + signing_string = core.table.concat(signing_string, "\n") local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) core.log.info("signature:", ngx_encode_base64(signature))