Add HMAC Signature authentication to a Service or a Route
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
This plugin is partially compatible with DB-less mode.
In DB-less mode, you configure Kong Gateway declaratively. Therefore, the Admin API is mostly read-only. The only tasks it can perform are all related to handling the declarative config, including:
- Setting a target's health status in the load balancer
- Validating configurations against schemas
- Uploading the declarative configuration using the
/config
endpoint
Consumers and Credentials can be created with declarative configuration.
Admin API endpoints which do POST, PUT, PATCH or DELETE on Credentials will not work on DB-less mode.
Example plugin configuration
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.name or service.id
Type: string |
The name or ID of the service the plugin targets.
Set one of these parameters if adding the plugin to a service through the top-level /plugins endpoint.
Not required if using /services/SERVICE_NAME|SERVICE_ID/plugins . |
route.name or route.id
Type: string |
The name or ID of the route the plugin targets.
Set one of these parameters if adding the plugin to a route through the top-level /plugins endpoint.
Not required if using /routes/ROUTE_NAME|ROUTE_ID/plugins . |
enabled
Type: boolean Default value: true |
Whether this plugin will be applied. |
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/Route. 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. A Consumer can have many credentials.
In both cases, the parameters are as described below:
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. |
Create a Credential
In both cases the fields/parameters work as follows:
field/parameter | description |
---|---|
{consumer} |
The id or username property of the Consumer entity to associate the credentials to. |
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.
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 "paths[]=/"
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
...
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
...
Making an authorized request
$ 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.
$ 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 by consumer by using this other path:
$ curl -X GET http://kong:8001/consumers/{username or id}/hmac-auth
{
"total": 1,
"data": [
{
"created_at": 1509419793000,
"id": "11d5cbfb-31b9-4a6d-8496-2f4a76500643",
"secret": "zi6YHyvLaUCe21XMXKesTYiHSWy6m6CW",
"username": "bar",
"consumer": { "id": "3c2c8fc1-7245-4fbb-b48b-e5947e1ce941" }
}
]
}
username or id
: The username or id of the consumer whose credentials need to be listed
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.