diff --git a/apisix/plugins/hmac-auth.lua b/apisix/plugins/hmac-auth.lua index c456aaa46e3c..35a11bd24a81 100644 --- a/apisix/plugins/hmac-auth.lua +++ b/apisix/plugins/hmac-auth.lua @@ -29,13 +29,16 @@ local hmac = require("resty.hmac") local consumer = require("apisix.consumer") local plugin = require("apisix.plugin") local ngx_decode_base64 = ngx.decode_base64 +local ngx_encode_base64 = ngx.encode_base64 +local BODY_DIGEST_KEY = "X-HMAC-DIGEST" local SIGNATURE_KEY = "X-HMAC-SIGNATURE" local ALGORITHM_KEY = "X-HMAC-ALGORITHM" local DATE_KEY = "Date" local ACCESS_KEY = "X-HMAC-ACCESS-KEY" local SIGNED_HEADERS_KEY = "X-HMAC-SIGNED-HEADERS" local plugin_name = "hmac-auth" +local MAX_REQ_BODY = 1024 * 512 local lrucache = core.lrucache.new({ type = "plugin", @@ -79,7 +82,17 @@ local consumer_schema = { type = "boolean", title = "Whether to escape the uri parameter", default = true, - } + }, + validate_request_body = { + type = "boolean", + title = "A boolean value telling the plugin to enable body validation", + default = false, + }, + max_req_body = { + type = "integer", + title = "Max request body size", + default = MAX_REQ_BODY, + }, }, required = {"access_key", "secret_key"}, } @@ -193,7 +206,6 @@ local function do_nothing(v) return v end - local function generate_signature(ctx, secret_key, params) local canonical_uri = ctx.var.uri local canonical_query_string = "" @@ -327,6 +339,27 @@ local function validate(ctx, params) return nil, {message = "Invalid signature"} end + local validate_request_body = get_conf_field(params.access_key, "validate_request_body") + if validate_request_body then + local digest_header = params.body_digest + if not digest_header then + return nil, {message = "Invalid digest"} + end + + local max_req_body = get_conf_field(params.access_key, "max_req_body") + local req_body, err = core.request.get_body(max_req_body, ctx) + if err then + return nil, {message = "Exceed body limit size"} + end + + req_body = req_body or "" + local request_body_hash = ngx_encode_base64( + hmac_funcs[params.algorithm](secret_key, req_body)) + if request_body_hash ~= digest_header then + return nil, {message = "Invalid digest"} + end + end + return consumer end @@ -338,6 +371,7 @@ local function get_params(ctx) local algorithm_key = ALGORITHM_KEY local date_key = DATE_KEY local signed_headers_key = SIGNED_HEADERS_KEY + local body_digest_key = BODY_DIGEST_KEY local attr = plugin.plugin_attr(plugin_name) @@ -347,6 +381,7 @@ local function get_params(ctx) algorithm_key = attr.algorithm_key or algorithm_key date_key = attr.date_key or date_key signed_headers_key = attr.signed_headers_key or signed_headers_key + body_digest_key = attr.body_digest_key or body_digest_key end local app_key = core.request.header(ctx, access_key) @@ -354,6 +389,7 @@ local function get_params(ctx) local algorithm = core.request.header(ctx, algorithm_key) local date = core.request.header(ctx, date_key) local signed_headers = core.request.header(ctx, signed_headers_key) + local body_digest = core.request.header(ctx, body_digest_key) core.log.info("signature_key: ", signature_key) -- get params from header `Authorization` @@ -382,6 +418,7 @@ local function get_params(ctx) params.signature = signature params.date = date or "" params.signed_headers = signed_headers and ngx_re.split(signed_headers, ";") + params.body_digest = body_digest local keep_headers = get_conf_field(params.access_key, "keep_headers") core.log.info("keep_headers: ", keep_headers) diff --git a/docs/en/latest/plugins/hmac-auth.md b/docs/en/latest/plugins/hmac-auth.md index 6688ab93a2fa..44481784855f 100644 --- a/docs/en/latest/plugins/hmac-auth.md +++ b/docs/en/latest/plugins/hmac-auth.md @@ -51,6 +51,8 @@ The `consumer` then adds its key to request header to verify its request. | signed_headers | array[string] | optional | | | Restrict the headers that are added to the encrypted calculation. After the specified, the client request can only specify the headers within this range. When this item is empty, all the headers specified by the client request will be added to the encrypted calculation | | keep_headers | boolean | optional | false | [ true, false ] | Whether it is necessary to keep the request headers of `X-HMAC-SIGNATURE`, `X-HMAC-ALGORITHM` and `X-HMAC-SIGNED-HEADERS` in the http request after successful authentication. true: means to keep the http request header, false: means to remove the http request header. | | encode_uri_params | boolean | optional | true | [ true, false ] | Whether to encode the uri parameter in the signature, for example: `params1=hello%2Cworld` is encoded, `params2=hello,world` is not encoded. true: means to encode the uri parameter in the signature, false: not to encode the uri parameter in the signature. | +| validate_request_body | boolean | optional | false | [ true, false ] | Whether to check request body. | +| max_req_body | integer | optional | 512 * 1024 | | Max allowed body size. | ## How To Enable @@ -192,6 +194,18 @@ print(base64.b64encode(hash.digest())) | --------- | -------------------------------------------- | | SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= | +### Request body checking + +When `validate_request_body` is assigned to `true`, the plugin will check the request body. The plugin will calculate the hmac-sha value of the request body,and check against the `X-HMAC-DIGEST` header. + +``` +X-HMAC-DIGEST: base64(hmac-sha()) +``` + +When there is no request body, you can set `X-HMAC-DIGEST` value to the hmac-sha of empty string. + +**Note:** The plugin will load the request body to memory to calculate the digest of the request body, which might cause high memory consumption with large bodies. You can limit the max allowed body size by the configuration of `max_req_body`(default=512KB), request body larger than that will be rejected. + ### Use the generated signature to try the request ```shell @@ -262,12 +276,13 @@ plugin_attr: date_key: X-APISIX-DATE access_key: X-APISIX-HMAC-ACCESS-KEY signed_headers_key: X-APISIX-HMAC-SIGNED-HEADERS + body_digest_key: X-APISIX-HMAC-BODY-DIGEST ``` **After customizing the header, request example:** ```shell -$ curl http://127.0.0.1:9080/index.html -H 'X-APISIX-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-APISIX-HMAC-ALGORITHM: ALGORITHM' -H 'X-APISIX-DATE: DATE' -H 'X-APISIX-HMAC-ACCESS-KEY: ACCESS_KEY' -H 'X-APISIX-HMAC-SIGNED-HEADERS: SIGNED_HEADERS' -i +$ curl http://127.0.0.1:9080/index.html -H 'X-APISIX-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-APISIX-HMAC-ALGORITHM: ALGORITHM' -H 'X-APISIX-DATE: DATE' -H 'X-APISIX-HMAC-ACCESS-KEY: ACCESS_KEY' -H 'X-APISIX-HMAC-SIGNED-HEADERS: SIGNED_HEADERS' -H 'X-APISIX-HMAC-BODY-DIGEST: BODY_DIGEST' -i HTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 @@ -278,6 +293,29 @@ Accept-Ranges: bytes ``` +### Enable request body checking + +```shell +$ curl -X "POST" "http://localhost:9080/index.html?age=36&name=james" \ + -H 'X-HMAC-ACCESS-KEY: my-access-key' \ + -H 'X-HMAC-SIGNATURE: lSWO4vcyVoZG5bn8miHudzABAeJQd8tqEHyM7RsjeiU=' \ + -H 'X-HMAC-ALGORITHM: hmac-sha256' \ + -H 'Date: Tue, 24 Aug 2021 03:19:21 GMT' \ + -H 'X-HMAC-SIGNED-HEADERS: User-Agent;X-HMAC-DIGEST' \ + -H 'User-Agent: curl/7.29.0' \ + -H 'X-HMAC-DIGEST: L9b/+QMvhvnoUlSw5vq+kHPqnZiHGl61T8oavMVTaC4=' \ + -H 'Content-Type: text/plain; charset=utf-8' \ + -d "{\"hello\":\"world\"}" + +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Date: Tue, 14 Sep 2021 03:28:14 GMT +Server: APISIX/2.9 +... +``` + ## Disable Plugin When you want to disable the `hmac-auth` plugin, it is very simple, diff --git a/docs/zh/latest/plugins/hmac-auth.md b/docs/zh/latest/plugins/hmac-auth.md index 2536b2e345a3..574e82c04dde 100644 --- a/docs/zh/latest/plugins/hmac-auth.md +++ b/docs/zh/latest/plugins/hmac-auth.md @@ -51,6 +51,8 @@ title: hmac-auth | signed_headers | array[string] | 可选 | | | 限制加入加密计算的 headers ,指定后客户端请求只能在此范围内指定 headers ,此项为空时将把所有客户端请求指定的 headers 加入加密计算。如: ["User-Agent", "Accept-Language", "x-custom-a"] | | keep_headers | boolean | 可选 | false | [ true, false ] | 认证成功后的 http 请求中是否需要保留 `X-HMAC-SIGNATURE`、`X-HMAC-ALGORITHM` 和 `X-HMAC-SIGNED-HEADERS` 的请求头。true: 表示保留 http 请求头,false: 表示移除 http 请求头。 | | encode_uri_param | boolean | 可选 | true | [ true, false ] | 是否对签名中的 uri 参数进行编码,例如: `params1=hello%2Cworld` 进行了编码,`params2=hello,world` 没有进行编码。true: 表示对签名中的 uri 参数进行编码,false: 不对签名中的 uri 参数编码。 | +| validate_request_body | boolean | 可选 | false | [ true, false ] | 是否对请求 body 做签名校验。| +| max_req_body | integer | 可选 | 512 * 1024 | | 最大允许的 body 大小。| ## 如何启用 @@ -186,6 +188,18 @@ print(base64.b64encode(hash.digest())) | --------- | -------------------------------------------- | | SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= | +### Body 校验 + +`validate_request_body` 设置为 true 时,插件将计算请求 body 的 `hmac-sha` 值,并与请求 headers 中的 `X-HMAC-DIGEST` 的值进行校验。 + +``` +X-HMAC-DIGEST: base64(hmac-sha()) +``` + +当没有请求 body 时,插件会计算长度为 0 的空字符串的 hmac-sha 值。 + +**注:**当开启 body 校验时,为了计算请求 body 的 `hmac-sha` 值,插件会把 body 加载到内存中,在请求 body 较大的情况下,可能会造成较高的内存消耗。插件提供了 `max_req_body`(默认值 512KB) 配置项来配置最大允许的 body 大小,body 超过此大小的请求会被拒绝。 + ### 使用生成好的签名进行请求尝试 ```shell @@ -256,12 +270,13 @@ plugin_attr: date_key: X-APISIX-DATE access_key: X-APISIX-HMAC-ACCESS-KEY signed_headers_key: X-APISIX-HMAC-SIGNED-HEADERS + body_digest_key: X-APISIX-HMAC-BODY-DIGEST ``` **自定义 header 后,请求示例:** ```shell -$ curl http://127.0.0.1:9080/index.html -H 'X-APISIX-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-APISIX-HMAC-ALGORITHM: ALGORITHM' -H 'X-APISIX-DATE: DATE' -H 'X-APISIX-HMAC-ACCESS-KEY: ACCESS_KEY' -H 'X-APISIX-HMAC-SIGNED-HEADERS: SIGNED_HEADERS' -i +$ curl http://127.0.0.1:9080/index.html -H 'X-APISIX-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-APISIX-HMAC-ALGORITHM: ALGORITHM' -H 'X-APISIX-DATE: DATE' -H 'X-APISIX-HMAC-ACCESS-KEY: ACCESS_KEY' -H 'X-APISIX-HMAC-SIGNED-HEADERS: SIGNED_HEADERS' -H 'X-APISIX-HMAC-BODY-DIGEST: BODY_DIGEST' -i HTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 @@ -272,6 +287,29 @@ Accept-Ranges: bytes ``` +### 开启 body 校验 + +```shell +$ curl -X "POST" "http://localhost:9080/index.html?age=36&name=james" \ + -H 'X-HMAC-ACCESS-KEY: my-access-key' \ + -H 'X-HMAC-SIGNATURE: lSWO4vcyVoZG5bn8miHudzABAeJQd8tqEHyM7RsjeiU=' \ + -H 'X-HMAC-ALGORITHM: hmac-sha256' \ + -H 'Date: Tue, 24 Aug 2021 03:19:21 GMT' \ + -H 'X-HMAC-SIGNED-HEADERS: User-Agent;X-HMAC-DIGEST' \ + -H 'User-Agent: curl/7.29.0' \ + -H 'X-HMAC-DIGEST: L9b/+QMvhvnoUlSw5vq+kHPqnZiHGl61T8oavMVTaC4=' \ + -H 'Content-Type: text/plain; charset=utf-8' \ + -d "{\"hello\":\"world\"}" + +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Date: Tue, 14 Sep 2021 03:28:14 GMT +Server: APISIX/2.9 +... +``` + ## 禁用插件 当你想去掉 `hmac-auth` 插件的时候,很简单,在插件的配置中把对应的 `json` 配置删除即可,无须重启服务,即刻生效: diff --git a/t/plugin/hmac-auth3.t b/t/plugin/hmac-auth3.t new file mode 100644 index 000000000000..bc66dc6569f1 --- /dev/null +++ b/t/plugin/hmac-auth3.t @@ -0,0 +1,534 @@ +# +# 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(1); +no_long_string(); +no_shuffle(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } + + if (!$block->no_error_log && !$block->error_log) { + $block->set_value("no_error_log", "[error]\n[alert]"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: add consumer with validate_request_body +--- 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-key", + "secret_key": "my-secret-key", + "validate_request_body": true + } + } + }]], + [[{ + "node": { + "value": { + "username": "robin", + "plugins": { + "hmac-auth": { + "access_key": "my-access-key", + "secret_key": "my-secret-key", + "algorithm": "hmac-sha256", + "validate_request_body": true + } + } + } + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 2: 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) + } + } +--- response_body +passed + + + +=== TEST 3: missing body digest when validate_request_body is enabled +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = "{\"name\": \"world\"}" + + local signing_string = { + "POST", + "/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") .. "\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 headers = {} + headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature) + headers["X-HMAC-ALGORITHM"] = "hmac-sha256" + headers["Date"] = gmt + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- error_code: 401 +--- response_body eval +qr/\{"message":"Invalid digest"\}/ + + + +=== TEST 4: verify body digest: not ok +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = "{\"name\": \"world\"}" + + local signing_string = { + "POST", + "/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") .. "\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 headers = {} + headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature) + headers["X-HMAC-ALGORITHM"] = "hmac-sha256" + headers["Date"] = gmt + headers["X-HMAC-DIGEST"] = "hello" + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- error_code: 401 +--- response_body eval +qr/\{"message":"Invalid digest"\}/ + + + +=== TEST 5: verify body digest: ok +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = "{\"name\": \"world\"}" + + local signing_string = { + "POST", + "/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") .. "\n" + core.log.info("signing_string:", signing_string) + + local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) + local body_digest = hmac:new(secret_key, hmac.ALGOS.SHA256):final(body) + + 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["Date"] = gmt + headers["X-HMAC-DIGEST"] = ngx_encode_base64(body_digest) + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 6: add consumer with max_req_body +--- 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-key", + "secret_key": "my-secret-key", + "validate_request_body": true, + "max_req_body": 1024 + } + } + }]], + [[{ + "node": { + "value": { + "username": "robin", + "plugins": { + "hmac-auth": { + "access_key": "my-access-key", + "secret_key": "my-secret-key", + "algorithm": "hmac-sha256", + "validate_request_body": true, + "max_req_body": 1024 + } + } + } + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 7: Exceed body limit size +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = ("-1Aa#"):rep(205) + + local signing_string = { + "POST", + "/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") .. "\n" + core.log.info("signing_string:", signing_string) + + local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) + local body_digest = hmac:new(secret_key, hmac.ALGOS.SHA256):final(body) + + 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["Date"] = gmt + headers["X-HMAC-DIGEST"] = ngx_encode_base64(body_digest) + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- error_code: 401 +--- response_body eval +qr/\{"message":"Exceed body limit size"}/ + + + +=== TEST 8: Test custom request body digest header name with mismatched header. +--- yaml_config +plugin_attr: + hmac-auth: + body_digest_key: "X-Digest-Custom" +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = "{\"name\": \"world\"}" + + local signing_string = { + "POST", + "/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") .. "\n" + core.log.info("signing_string:", signing_string) + + local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) + local body_digest = hmac:new(secret_key, hmac.ALGOS.SHA256):final(body) + + 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["Date"] = gmt + headers["X-HMAC-DIGEST"] = ngx_encode_base64(body_digest) + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- error_code: 401 +--- response_body eval +qr/\{"message":"Invalid digest"\}/ + + + +=== TEST 9: Test custom request body digest header name. +--- yaml_config +plugin_attr: + hmac-auth: + body_digest_key: "X-Digest-Custom" +--- config + location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_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 gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + local body = "{\"name\": \"world\"}" + + local signing_string = { + "POST", + "/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") .. "\n" + core.log.info("signing_string:", signing_string) + + local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) + local body_digest = hmac:new(secret_key, hmac.ALGOS.SHA256):final(body) + + 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["Date"] = gmt + headers["X-Digest-Custom"] = ngx_encode_base64(body_digest) + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/hello', + ngx.HTTP_POST, + body, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed