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.
Name | Type | Requirement | Default | Valid | Description |
---|---|---|---|---|---|
access_key | string | required | 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 | string | required | Use as a pair with access_key . | ||
algorithm | string | optional | “hmac-sha256” | [“hmac-sha1”, “hmac-sha256”, “hmac-sha512”] | Encryption algorithm. |
clock_skew | integer | optional | 0 | 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 Date | |
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. |
hmac-auth
optioncurl 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": 0, "signed_headers": ["User-Agent", "Accept-Language", "x-custom-a"] } } }'
The default keep_headers
is false and encode_uri_params
is true.
You can visit the dashboard to complete the above operations through the web interface, first add a consumer:
Then add the hmac-auth plugin to the consumer page:
hmac-auth
plugincurl 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": { "127.0.0.1:1980": 1 } } }'
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 + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string
. If one of the items in signing_string does not exist, you also need to use an empty string instead.
HTTP URI
requirements must start with “/”, those that do not start with “/” need to be added, and the empty path is “/”.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).signed_headers_string
is the result of obtaining the fields specified by the client from the request header and concatenating the strings in order.The coding steps of canonical_query_string are as follows:
query
item in the URL, that is, the string “key1 = valve1 & key2 = valve2” after the “?” in the URL.query
into several items according to the & separator, each item is in the form of key=value or only key.encode_uri_params
is true:encode_uri_params
is false:The signed_headers_string generation steps are as follows:
SIGNED_HEADERS
in the next section Use the generated signature to make a request attempt
.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.HeaderKey1 + ":" + HeaderValue1 + "\n"\+ HeaderKey2 + ":" + HeaderValue2 + "\n"\+ ... HeaderKeyN + ":" + HeaderValueN + "\n"
Signature string splicing example
Take the following request as an example:
$ curl -i http://127.0.0.1:9080/index.html?name=james&age=36 \ -H "X-HMAC-SIGNED-HEADERS: User-Agent;x-custom-a" \ -H "x-custom-a: test" \ -H "User-Agent: curl/7.29.0"
The signing_string
generated according to the signature generation formula
is:
"GET /index.html age=36&name=james user-key Tue, 19 Jan 2021 11:33:20 GMT User-Agent:curl/7.29.0 x-custom-a:test "
Note: The last request header also needs + \n
.
Generate Signature
Use Python to generate the signature SIGNATURE
:
import base64 import hashlib import hmac secret = bytes('my-secret-key', 'utf-8') message = bytes("""GET /index.html age=36&name=james user-key Tue, 19 Jan 2021 11:33:20 GMT User-Agent:curl/7.29.0 x-custom-a:test """, 'utf-8') hash = hmac.new(secret, message, hashlib.sha256) # to lowercase base64 print(base64.b64encode(hash.digest()))
Type | Hash |
---|---|
SIGNATURE | 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= |
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(<body>))
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.
$ curl -i "http://127.0.0.1:9080/index.html?name=james&age=36" \ -H "X-HMAC-SIGNATURE: 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg=" \ -H "X-HMAC-ALGORITHM: hmac-sha256" \ -H "X-HMAC-ACCESS-KEY: user-key" \ -H "Date: Tue, 19 Jan 2021 11:33:20 GMT" \ -H "X-HMAC-SIGNED-HEADERS: User-Agent;x-custom-a" \ -H "x-custom-a: test" \ -H "User-Agent: curl/7.29.0" HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Date: Tue, 19 Jan 2021 11:33:20 GMT Server: APISIX/2.2 ......
Below are two assembly forms of signature information
Authorization
field:$ curl http://127.0.0.1:9080/index.html -H 'Authorization: hmac-auth-v1# + ACCESS_KEY + # + base64_encode(SIGNATURE) + # + ALGORITHM + # + DATE + # + SIGNED_HEADERS' -i HTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes <!DOCTYPE html> <html lang="cn"> ...
$ curl http://127.0.0.1:9080/index.html -H 'X-HMAC-SIGNATURE: base64_encode(SIGNATURE)' -H 'X-HMAC-ALGORITHM: ALGORITHM' -H 'Date: DATE' -H 'X-HMAC-ACCESS-KEY: ACCESS_KEY' -H 'X-HMAC-SIGNED-HEADERS: SIGNED_HEADERS' -i HTTP/1.1 200 OK Content-Type: text/html Content-Length: 13175 ... Accept-Ranges: bytes <!DOCTYPE html> <html lang="cn">
Note:
x-custom-header-a;x-custom-header-b
base64_encode(SIGNATURE)
We can customize header key for auth parameters by adding the attribute configuration of the plugin under plugin_attr
in conf / config.yaml
.
plugin_attr: hmac-auth: signature_key: X-APISIX-HMAC-SIGNATURE algorithm_key: X-APISIX-HMAC-ALGORITHM 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:
$ 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 ... Accept-Ranges: bytes <!DOCTYPE html> <html lang="cn">
$ 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 ...
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:
$ 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": { "127.0.0.1:1980": 1 } } }'
Take HMAC SHA256 as an example to introduce the signature generation examples in different languages. Need to pay attention to the handling of newline characters in signature strings in various languages, which can easily lead to the problem of {"message":"Invalid signature"}
.
Example inputs:
Variable | Value |
---|---|
secret | the shared secret key here |
message | this is signature string |
Example outputs:
Type | Hash |
---|---|
hexit | ad1b76c7e5054009380edca35d3f36cc5b6f45c82ee02ea3af64197ebddb9345 |
base64 | rRt2x+UFQAk4DtyjXT82zFtvRcgu4C6jr2QZfr3bk0U= |
Please refer to HMAC Generate Signature Examples