Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hmac-auth): Add validate request body for hmac auth plugin #5038

Merged
merged 12 commits into from
Sep 16, 2021
Merged

feat(hmac-auth): Add validate request body for hmac auth plugin #5038

merged 12 commits into from
Sep 16, 2021

Conversation

arthur-zhang
Copy link
Contributor

What this PR does / why we need it:

Add validate request body for hmac auth plugin, by calculate body hmac hash and put it in Digest Header

Pre-submission checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

if not req_body then
req_body = ""
end
local digest_header = core.request.header(ctx, DIGEST)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

digest header looks not good for me.

Copy link
Contributor Author

@arthur-zhang arthur-zhang Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I use this header is that:
The Digest HTTP header provides a digest of the requested resource.

Date: Tue, 19 Jan 2021 11:33:20 GMT
Server: APISIX/2.2
......
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help to update the English doc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will

Comment on lines 210 to 212
if not req_body then
req_body = ""
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not req_body then
req_body = ""
end
req_body = req_body or ""

Comment on lines 214 to 217
if not digest_header then
-- it's ok if there is no digest header and no body
return req_body == ""
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we judge this first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not, because we can omit digest header when body is empty.

@@ -186,6 +188,16 @@ print(base64.b64encode(hash.digest()))
| --------- | -------------------------------------------- |
| SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= |

### Body 校验

把 `validate_request_body` 设置为 true 来进行请求 body 的校验。 插件将计算 hmac-sha 值,对比头部中的 Digest 头部值。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`validate_request_body` 设置为 true 来进行请求 body 的校验。 插件将计算 hmac-sha 值,对比头部中的 Digest 头部值。
`validate_request_body` 设置为 true 来进行请求 body 的校验。插件将计算 hmac-sha 值,对比头部中的 Digest 头部值。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digest in code is digest, better to keep same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

req_body = ""
end
local digest_header = core.request.header(ctx, DIGEST)
if not digest_header then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could check this before reading the body

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not, because we can omit digest header when body is empty.

@tzssangglass
Copy link
Member

hi @arthur-zhang, I notice this 当无请求 body 时,可不传 Digest 头部,网关会校验是否确实无请求 body。如果要传 Digest 头部,可计算长度为 0 的空字符串的 hmac-sha 值。, should we add test cases about GET method?

@arthur-zhang
Copy link
Contributor Author

arthur-zhang commented Sep 13, 2021

hi @arthur-zhang, I notice this 当无请求 body 时,可不传 Digest 头部,网关会校验是否确实无请求 body。如果要传 Digest 头部,可计算长度为 0 的空字符串的 hmac-sha 值。, should we add test cases about GET method?

I Add test === TEST 4: missing digest header and body is empty for post request without body, GET is no difference.

@@ -186,6 +188,16 @@ print(base64.b64encode(hash.digest()))
| --------- | -------------------------------------------- |
| SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= |

### Body 校验

把 `validate_request_body` 设置为 true 来进行请求 body 的校验。插件将计算 hmac-sha 值,对比头部中的 Digest 头部值。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`validate_request_body` 设置为 true 来进行请求 body 的校验。插件将计算 hmac-sha 值,对比头部中的 Digest 头部值
`validate_request_body` 设置为 true 时,插件将计算请求 body `hmac-sha` 值,并与请求 headers 中的 Digest 的值进行校验




=== TEST 3: missing body digest when validate_request_body is true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
=== TEST 3: missing body digest when validate_request_body is true
=== TEST 3: missing body digest when validate_request_body is enable




=== TEST 4: missing digest header and body is empty
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
=== TEST 4: missing digest header and body is empty
=== TEST 4: no digest header and request body is empty


local code, body = t.test('/hello',
ngx.HTTP_POST,
body,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here body should be nil?

Digest: base64(hmac-sha(<body>))
```

当无请求 body 时,可不传 Digest 头部,网关会校验是否确实无请求 body。如果要传 Digest 头部,可计算长度为 0 的空字符串的 hmac-sha 值。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
当无请求 body 时,可不传 Digest 头部,网关会校验是否确实无请求 body。如果要传 Digest 头部,可计算长度为 0 的空字符串的 hmac-sha 值。
当没有请求 body 时,可不传 Digest 头部,网关会校验是否确实无请求 body。如果要传 Digest 头部,插件将请求 body 默认为长度为 0 的空字符串,并参与到计算 hmac-sha 值。

default = false,
},
max_req_body = {
type = "number",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type = "number",
type = "integer",

@@ -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 | number | optional | 512KB | | Max allowed body size. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use a number instead of string in the doc's default value cell

-H 'Content-Type: text/plain; charset=utf-8' \
-d "{\"hello\":\"world\"}"

HTTP/1.1 200 OK
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to only show the first line? APISIX 2.2 doesn't support this feature. We can avoid confusing users by only show the first line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it


local DIGEST = "Digest"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this header configurable and default to "X-HMAC-DIGEST"?

There is already a Digest header in the HTTP standard and it is not relative to the hmac.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@@ -327,6 +351,18 @@ 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 max_req_body = get_conf_field(params.access_key, "max_req_body")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could return here if no params.body_digest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should check whether the params.body_digest exists or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when validate_request_body == true and no params.body_digest, why not return false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there is no request body, the X-HMAC-DIGEST header can be omitted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arthur-zhang

OK. Thanks for your explanation. How about when validate_request_body is enabled, X-APISIX-HMAC-BODY-DIGEST is required? Just pass empty strings for it when no body.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix it

headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature)
headers["X-HMAC-ALGORITHM"] = "hmac-sha256"
headers["Date"] = gmt
headers["X-Digest"] = ngx_encode_base64(body_digest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be X-HMAC-DIGEST?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any string here is ok, I will change it to X-HMAC-DIGEST.

@@ -192,6 +194,16 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to mention the max_req_body limitation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add a note to that

X-HMAC-DIGEST: base64(hmac-sha(<body>))
```

When there is no request body, the `X-HMAC-DIGEST` header can be omitted. If you want to send request with this header whether the body is empty or not, you can set `X-HMAC-DIGEST` value to the hmac-sha of empty string.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When there is no request body, the `X-HMAC-DIGEST` header can be omitted. If you want to send request with this header whether the body is empty or not, you can set `X-HMAC-DIGEST` value to the hmac-sha of empty string.
When there is no request body, the `X-HMAC-DIGEST` header can be omitted. If you want to send a request with this header when the body is missing, you can set `X-HMAC-DIGEST` value to the hmac-sha of empty string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

},
max_req_body = {
type = "integer",
title = "Max request body allowed",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Max request body size" would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


When there is no request body, the `X-HMAC-DIGEST` header can be omitted. If you want to send a request with this header when the body is missing, 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 wight 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**Note:** The plugin will load the request body to memory to calculate the digest of the request body, which wight 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.
**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.

@spacewander spacewander merged commit d6c09fb into apache:master Sep 16, 2021
@arthur-zhang arthur-zhang deleted the dev-hmac-body-validate branch September 16, 2021 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants