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.


Terminology

  • plugin: a plugin executing actions inside Kong before or after a request has been proxied to the upstream API.
  • Service: the Kong entity representing an external upstream API or microservice.
  • Route: the Kong entity representing a way to map downstream requests to upstream services.
  • upstream service: this refers to your own API/service sitting behind Kong, to which client requests are forwarded.
  • API: a legacy entity used to represent your upstream services. Deprecated in favor of Services since 0.13.0.

Configuration

Enabling the plugin on a Service

Configure this plugin on a Service by making the following request:

$ curl -X POST http://kong:8001/services/{service}/plugins \
    --data "name=hmac-auth" 
  • service: the id or name of the Service that this plugin configuration will target.

Enabling the plugin on a Route

Configure this plugin on a Route with:

$ curl -X POST http://kong:8001/routes/{route_id}/plugins \
    --data "name=hmac-auth" 
  • route_id: the id of the Route that this plugin configuration will target.

Enabling the plugin on an API

If you are using an older version of Kong with the legacy API entity (deprecated since 0.13.0), you can configure this plugin on top of such an API by making the following request:

$ curl -X POST http://kong:8001/apis/{api}/plugins \
    --data "name=hmac-auth" 
  • api: either id or name of the API that this plugin configuration will target.

Global plugins

All plugins can be configured using the http://kong:8001/plugins/ endpoint. A plugin which is not associated to any Service, Route or Consumer (or API, if you are using an older version of Kong) 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 parameterdefaultdescription
nameThe name of the plugin to use, in this case hmac-auth
service_idThe id of the Service which this plugin will target.
route_idThe id of the Route which this plugin will target.
enabledtrueWhether this plugin will be applied.
api_idThe id of the API which this plugin will target. Note: The API Entity is deprecated since Kong 0.13.0.
config.hide_credentials
optional

false

An optional boolean value telling the plugin to show or hide the credential from the upstream service. If true, the plugin will strip the credential from the request (i.e. the Authorization header) before proxying it.

config.clock_skew
optional

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 4xx. Please note that this value must refer to the Consumer id attribute which is internal to Kong, and not its custom_id.

config.validate_request_body
optional

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

hmac-sha1,
hmac-sha256,
hmac-sha384,
hmac-sha512

A list of HMAC digest algorithms which the user wants to support. Allowed values are hmac-sha1, hmac-sha256, hmac-sha384, and hmac-sha512

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.

  1. If the header name is not request-line then append the lowercased header name followed with an ASCII colon : and an ASCII space .

  2. If the header name is request-line then append the HTTP request line (in ASCII format), otherwise append the header value.

  3. 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 Kong
  • X-Consumer-Custom-ID, the custom_id of the Consumer (if set)
  • X-Consumer-Username, the username of the Consumer (if set)
  • X-Credential-Username, the username of the Credential (only if the consumer is not the 'anonymous' consumer)
  • X-Anonymous-Consumer, will be set to true 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

Note: This endpoint was introduced in Kong 0.11.2.

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

Note: This endpoint was introduced in Kong 0.11.2.

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.

Keep up with the latest features