Add HMAC Signature authentication to a Service or a Route (or the deprecated API entity)
to establish the integrity of incoming requests. The plugin will validate the
digital signature sent in the Proxy-Authorization
or Authorization
header
(in this order). This plugin implementation is based off the
draft-cavage-http-signatures
draft with a slightly different signature scheme.
Configuration Reference
Enable the plugin on a service
Enable the plugin on a route
Enable the plugin globally
A plugin which is not associated to any service, route, or consumer is considered global, and will be run on every request. Read the Plugin Reference and the Plugin Precedence sections for more information.
Parameters
Here's a list of all the parameters which can be used in this plugin's configuration:
Form Parameter | Description |
---|---|
name
required Type: string |
The name of the plugin, in this case hmac-auth . |
service.id
Type: string |
The ID of the Service the plugin targets. |
route.id
Type: string |
The ID of the Route the plugin targets. |
enabled
required Type: boolean Default value: true |
Whether this plugin will be applied. |
api_id
Type: string |
The ID of the API the plugin targets.
Note: The API Entity is deprecated in favor of Services since CE 0.13.0 and EE 0.32. |
config.hide_credentials
optional Default value: false
|
An optional boolean value telling the plugin to show or hide the credential from the upstream service. If |
config.clock_skew
optional Default value: 300
|
Clock Skew in seconds to prevent replay attacks. |
config.anonymous
optional |
An optional string (consumer uuid) value to use as an “anonymous” consumer if authentication fails. If empty (default), the request will fail with an authentication failure |
config.validate_request_body
optional Default value: false
|
A boolean value telling the plugin to enable body validation |
config.enforce_headers
optional |
A list of headers which the client should at least use for HTTP signature creation |
config.algorithms
optional Default value: hmac-sha1,hmac-sha256,hmac-sha384,hmac-sha512
|
A list of HMAC digest algorithms which the user wants to support. Allowed values are |
Once applied, any user with a valid credential can access the Service/API. To restrict usage to only some of the authenticated users, also add the ACL plugin (not covered here) and create whitelist or blacklist groups of users.
Usage
In order to use the plugin, you first need to create a Consumer to associate one or more credentials to.
Note: Because the HMAC signature is generated by the client, you should make sure that Kong does not update or remove any request parameter used in HMAC signature before this plugin runs.
Create a Consumer
You need to associate a credential to an existing Consumer object. To create a Consumer, you can execute the following request:
$ curl -d "username=user123&custom_id=SOME_CUSTOM_ID" http://kong:8001/consumers/
parameter | description |
---|---|
username semi-optional |
The username of the consumer. Either this field or custom_id must be specified. |
custom_id semi-optional |
A custom identifier used to map the consumer to another database. Either this field or username must be specified. |
A Consumer can have many credentials.
Create a Credential
You can provision new username/password credentials by making the following HTTP request:
$ curl -X POST http://kong:8001/consumers/{consumer}/hmac-auth \
--data "username=bob" \
--data "secret=secret456"
consumer
: The id
or username
property of the Consumer
entity to associate the credentials to.
form parameter | description |
---|---|
username |
The username to use in the HMAC Signature verification. |
secret optional |
The secret to use in the HMAC Signature verification. Note that if this parameter isn’t provided, Kong will generate a value for you and send it as part of the response body. |
Signature Authentication Scheme
The client is expected to send an Authorization
or Proxy-Authorization
header
with the following parameterization:
credentials := "hmac" params
params := keyId "," algorithm ", " headers ", " signature
keyId := "username" "=" plain-string
algorithm := "algorithm" "=" DQUOTE (hmac-sha1|hmac-sha256|hmac-sha384|hmac-sha512) DQUOTE
headers := "headers" "=" plain-string
signature := "signature" "=" plain-string
plain-string = DQUOTE *( %x20-21 / %x23-5B / %x5D-7E ) DQUOTE
Signature Parameters
parameter | description |
---|---|
username | The username of the credential |
algorithm | Digital signature algorithm used to create the signature |
headers | List of HTTP header names, separated by a single space character, used to sign the request |
signature | Base64 encoded digital signature generated by the client |
Signature String Construction
In order to generate the string that is signed with a key, the client
MUST take the values of each HTTP header specified by headers
in
the order they appear.
-
If the header name is not
request-line
then append the lowercased header name followed with an ASCII colon:
and an ASCII space ` `. -
If the header name is
request-line
then append the HTTP request line (in ASCII format), otherwise append the header value. -
If value is not the last value then append an ASCII newline
\n
. The string MUST NOT include a trailing ASCII newline.
Clock Skew
The HMAC Authentication plugin also implements a clock skew check as described
in the specification to prevent replay attacks. By default,
a minimum lag of 300s in either direction (past/future) is allowed. Any request
with a higher or lower date value will be rejected. The length of the clock
skew can be edited through the plugin’s configuration by setting the
clock_skew
property (config.clock_skew
POST parameters).
The server and requesting client should be synchronized with NTP and a valid
date (using GMT format) should be sent with either the X-Date
or Date
header.
Body Validation
User can set config.validate_request_body
as true
to validate the request
body. If it’s enabled the plugin will calculate the SHA-256
HMAC digest of
the request body and match it against the value of the Digest
header. The
Digest header needs to be in following format:
Digest: SHA-256=base64(sha256(<body>))
If there is no request body, the Digest
should be set to the digest of a
body of 0 length.
Note: In order to create the digest of a request body, the plugin needs to retain it in memory, which might cause pressure on the worker’s Lua VM when dealing with large bodies (several MBs) or during high request concurrency.
Enforcing Headers
config.enforce_headers
can be used to enforce any of the headers to be part
of the signature creation. By default, the plugin doesn’t enforce which header
needs to be used for the signature creation. The minimum recommended data to
sign is the request-line
, host
, and date
. A strong signature would
include all of the headers and a digest
of the body.
HMAC Example
The HMAC plugin can be enabled on a Service or a Route (or the deprecated API entity).
Create a Service
$ curl -i -X POST http://localhost:8001/services \
-d "name=example-service" \
-d "url=http://example.com"
HTTP/1.1 201 Created
...
Then create a Route
$ curl -i -f -X POST http://localhost:8001/services/example-service/routes \
-d "name=hmac-test" \
-d "paths[]=/"
HTTP/1.1 201 Created
...
Add an API
For versions below 0.13.0, you would use now-deprecated API entity:
$ curl -i -X POST http://localhost:8001/apis \
-d "name=hmac-test" \
-d "hosts=hmac.com" \
-d "upstream_url=http://example.com"
HTTP/1.1 201 Created
...
Enabling the plugin on a Service
Plugins can be enabled on a Service or a Route. This example uses a Service.
$ curl -i -X POST http://localhost:8001/services/example-service/plugins \
-d "name=hmac-auth" \
-d "config.enforce_headers=date, request-line" \
-d "config.algorithms=hmac-sha1, hmac-sha256"
HTTP/1.1 201 Created
...
Enabling the plugin on an API
$ curl -i -X POST http://localhost:8001/apis/hmac-test/plugins \
-d "name=hmac-auth" \
-d "config.enforce_headers=date, request-line" \
-d "config.algorithms=hmac-sha1, hmac-sha256"
HTTP/1.1 201 Created
...
Here we are enabling the hmac-auth
plugin on API the hmac-test
.
config.enforce_headers
is set to force the client to at least use date
and request-line
in the HTTP signature creation. Also we are setting the
config.algorithms
to force the client to only use hmac-sha1
or
hmac-sha256
for hashing the signing string.
Add a Consumer
$ curl -i -X POST http://localhost:8001/consumers/ \
-d "username=alice"
HTTP/1.1 201 Created
...
Add credential for Alice
$ curl -i -X POST http://localhost:8001/consumers/alice/hmac-auth \
-d "username=alice123" \
-d "secret=secret"
HTTP/1.1 201 Created
...
Request to the API
$ curl -i -X GET http://localhost:8000/requests \
-H "Host: hmac.com" \
-H "Date: Thu, 22 Jun 2017 17:15:21 GMT" \
-H 'Authorization: hmac username="alice123", algorithm="hmac-sha256", headers="date request-line", signature="ujWCGHeec9Xd6UD2zlyxiNMCiXnDOWeVFMu5VeRUxtw="'
HTTP/1.1 200 OK
...
In the above request, we are composing the signing string using the date
and
request-line
headers and creating the digest using the hmac-sha256
to
hash the digest:
signing_string="date: Thu, 22 Jun 2017 17:15:21 GMT\nGET /requests HTTP/1.1"
digest=HMAC-SHA256(<signing_string>, "secret")
base64_digest=base64(<digest>)
So the final value of the Authorization
header would look like:
Authorization: hmac username="alice123", algorithm="hmac-sha256", headers="date request-line", signature=<base64_digest>"
Validating request body
To enable body validation we would need to set config.validate_request_body
to true
:
The following example works the same way, whether the plugin was added to a Service or a Route (or an API).
$ curl -i -X PATCH http://localhost:8001/plugins/{plugin-id} \
-d "config.validate_request_body=true"
HTTP/1.1 200 OK
...
Now if the client includes the body digest in the request as the value of the
Digest
header, the plugin will validate the request body by calculating the
SHA-256
of the body and matching it against the Digest
header’s value.
$ curl -i -X GET http://localhost:8000/requests \
-H "Host: hmac.com" \
-H "Date: Thu, 22 Jun 2017 21:12:36 GMT" \
-H "Digest: SHA-256=SBH7QEtqnYUpEcIhDbmStNd1MxtHg2+feBfWc1105MA=" \
-H 'Authorization: hmac username="alice123", algorithm="hmac-sha256", headers="date request-line digest", signature="gaweQbATuaGmLrUr3HE0DzU1keWGCt3H96M28sSHTG8="' \
-d "A small body"
HTTP/1.1 200 OK
...
In the above request we calculated the SHA-256
digest of the body and set
the Digest
header with the following format:
body="A small body"
digest=SHA-256(body)
base64_digest=base64(digest)
Digest: SHA-256=<base64_digest>
Upstream Headers
When a client has been authenticated, the plugin will append some headers to the request before proxying it to the upstream service, so that you can identify the Consumer in your code:
X-Consumer-ID
, the ID of the Consumer on KongX-Consumer-Custom-ID
, thecustom_id
of the Consumer (if set)X-Consumer-Username
, theusername
of the Consumer (if set)X-Credential-Username
, theusername
of the Credential (only if the consumer is not the ‘anonymous’ consumer)X-Anonymous-Consumer
, will be set totrue
when authentication failed, and the ‘anonymous’ consumer was set instead.
You can use this information on your side to implement additional logic.
You can use the X-Consumer-ID
value to query the Kong Admin API and retrieve
more information about the Consumer.
Paginate through the HMAC Credentials
You can paginate through the hmac-auth Credentials for all Consumers using the following request:
$ curl -X GET http://kong:8001/hmac-auths
{
"total": 3,
"data": [
{
"created_at": 1509681246000,
"id": "75695322-e8a0-4109-aed4-5416b0308d85",
"secret": "wQazJ304DW5huJklHgUfjfiSyCyTAEDZ",
"username": "foo",
"consumer_id": "c0d92ba9-8306-482a-b60d-0cfdd2f0e880"
},
{
"created_at": 1509419793000,
"id": "11d5cbfb-31b9-4a6d-8496-2f4a76500643",
"secret": "zi6YHyvLaUCe21XMXKesTYiHSWy6m6CW",
"username": "bar",
"consumer_id": "3c2c8fc1-7245-4fbb-b48b-e5947e1ce941"
},
{
"created_at": 1509681215000,
"id": "eb0365bc-88ae-4568-be7c-db1eb7c16e5e",
"secret": "NvHDTg5mp0ySFVJsITurtgyhEq1Cxbnv",
"username": "baz",
"consumer_id": "c0d92ba9-8306-482a-b60d-0cfdd2f0e880"
}
]
}
You can filter the list using the following query parameters:
Attributes | Description |
---|---|
id optional |
A filter on the list based on the hmac-auth credential id field. |
username optional |
A filter on the list based on the hmac-auth credential username field. |
consumer_id optional |
A filter on the list based on the hmac-auth credential consumer_id field. |
size optional, default is 100 |
A limit on the number of objects to be returned. |
offset optional |
A cursor used for pagination. offset is an object identifier that defines a place in the list. |
Retrieve the Consumer associated with a Credential
It is possible to retrieve a Consumer associated with an HMAC Credential using the following request:
curl -X GET http://kong:8001/hmac-auths/{hmac username or id}/consumer
{
"created_at":1507936639000,
"username":"foo",
"id":"c0d92ba9-8306-482a-b60d-0cfdd2f0e880"
}
hmac username or id
: The id
or username
property of the HMAC Credential
for which to get the associated Consumer.
Note that username
accepted here is not the username
property of a
Consumer.