How to Use HMAC Authentication Plugin

I’ve covered a few authentication plugins in the past. I guess it is time to write something for another free plugin. This time I will cover the usage of HMAC authentication plugin.

Documentation of this plugin is pretty good, the purpose of this post is to give you a working example out of box so you can try yourself.

Usage example

As usual, I will use Admin API to set up this plugin in the first part.

Prerequisite :

Kong instance running with database.

You can find plugin documentation here.

Let’s begin:

Create a service

1
2
3
4
curl -X POST \
--url http://localhost:8001/services \
--header "Content-Type: application/json" \
--data '{"name":"test-service","url":"https://httpbin.org/anything"}'

Create Route

1
2
3
4
curl -X POST \
--url http://localhost:8001/services/test-service/routes \
--header "Content-Type: application/json" \
--data '{"name":"test-route","paths":["/test"]}'

When we visit our route curl http://localhost:8000/test, we should get HTTP/1.1 200 OK.

Enable plugin

I enable this plugin globally, you can also enable it on a service or route.

1
2
3
4
curl -X POST \
--url http://localhost:8001/plugins \
--header "Content-Type: application/json" \
--data '{"name":"hmac-auth","config":{"validate_request_body":false}}'

If we visit the route again, we will get HTTP/1.1 401 Unauthorized.

Create consumer

1
2
3
curl -X POST http://localhost:8001/consumers \
-H "Content-Type: application/json" \
-d '{"username":"tester"}'

Create HMAC credentials

You can create multiple HMAC credentials for the same consumer.

1
2
3
curl -X POST http://localhost:8001/consumers/tester/hmac-auth \
-H "Content-Type: application/json" \
-d '{"username":"hmac_username","secret":"hmac_secret"}'

Calculate Signature

This is the most important part of using this plugin. Depends on what users set for config.enforce_headers, some of the headers must be used to generate HMAC signature. Each entry MUST be on a new line.

For example, let’s say we want to send curl -X GET http://localhost:8000/test/anything and we have set config.enforce_headers=date request-url host.

Let’s use curl to check the request headers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
curl -vs http://localhost:8000/test/anything > /dev/null
* Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET /test/anything HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Date: Thu, 08 Jul 2021 06:22:05 GMT
< Content-Type: application/json; charset=utf-8
< Connection: keep-alive
< Content-Length: 30
< X-Kong-Response-Latency: 2
< Server: kong/2.4.1.1-enterprise-edition
<
{ [30 bytes data]
* Connection #0 to host localhost left intact

As we can see here GET /test/anything HTTP/1.1 is the request-url, Host: localhost:8000 is host. The date header needs to be used when we send the request. Let’s say it is the same as the response time Date: Thu, 08 Jul 2021 06:22:05 GMT.

The signature string for calculating our HMAC signature is below.

1
Date: Thu, 08 Jul 2021 06:16:07 GMT\nhost: localhost:8000\nGET /test/anything HTTP/1.1

Now that we have the signature string, you can calculate HMAC signature.

If you set config.validate_request_body=true, you need to calculate request body’s sha256 digest and put a digest header in your request. You need to also include this digest header in your signature string.

I will provide a shell script at the end of this article. You can use it for calculating HMAC signature.

Make API call with Authorization header

Let’s say the signature we got is QiMr2Oq4nm55NAFSiGUnhgnDFTGFHQpS6Qvb2KIprak=, we can make api call again as below:

1
2
3
curl http://localhost:8000/test/anything \
--header 'Date: Thu, 08 Jul 2021 06:22:05 GMT' \
--header 'Authorization: hmac username="hmac_username", algorithm="hmac-sha256", headers="date request-line host", signature="QiMr2Oq4nm55NAFSiGUnhgnDFTGFHQpS6Qvb2KIprak="'

Other deployments methods

DBless deployment

Please save below content to kong.yaml and load it to your dbless deployment configuration or send it at /config endpoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_format_version: "2.1"
_transform: true

services:
- name: test-service
url: https://httpbin.org/anything
routes:
- name: test-route
paths:
- /test

plugins:
- name: hmac-auth
config:
algorithms:
- hmac-sha256
validate_request_body: false

consumers:
- username: tester
hmacauth_credentials:
- username: hmac_username
secret: hmac_secret

After that you can calculate the signature string as described above.

Kubernetes Ingress Controller

Below example deploys :

  • Echo deployment
  • Echo Service
  • HMAC plugin globally
  • Kong Consumer
  • Kong hmac_credential to consumer
  • Kong Ingress

Please save below to demo.yaml file and run kubectl apply -f demo.yaml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-deployment
spec:
replicas: 1
selector:
matchLabels:
app: echo-pod
template:
metadata:
labels:
app: echo-pod
spec:
containers:
- name: echoheaders
image: k8s.gcr.io/echoserver:1.10
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
resources:
limits:
memory: 512Mi
cpu: "1"
requests:
memory: 256Mi
cpu: "0.2"
---
apiVersion: v1
kind: Service
metadata:
name: echo-service
spec:
selector:
app: echo-pod
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: proxy-from-k8s-to-httpbin
annotations:
kubernetes.io/ingress.class: "kong"
konghq.com/strip-path: "true"
spec:
rules:
- http:
paths:
- path: /test
backend:
serviceName: echo-service
servicePort: 80
---
apiVersion: configuration.konghq.com/v1
kind: KongClusterPlugin
metadata:
name: hmac-auth-plugin
annotations:
kubernetes.io/ingress.class: "kong"
labels:
global: "true"
config:
validate_request_body: false
algorithms:
- hmac-sha256
plugin: hmac-auth
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: hmac-user
annotations:
kubernetes.io/ingress.class: "kong"
username: hmac-user
credentials:
- a-hmac-secret
---
apiVersion: v1
kind: Secret
metadata:
name: a-hmac-secret
type: Opaque
stringData:
kongCredType: hmac-auth
username: hmac_username
secret: hmac_secret

HMAC calculation script

Please save below code to hmac.sh and you can use it to calculate all the headers you need.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#! /bin/sh
printf "Enter your user_name and press [ENTER]: \n"
read username
printf "Enter your secret and press [ENTER]: \n"
read token
printf "Enter your host_name (e.g.: example.com:8000) and press [ENTER]: \n"
read hostname
printf "Enter your request line (e.g.: GET /test HTTP/1.1) and press [ENTER]: \n"
read request
printf "Enter your request body and press [ENTER]: \n"
read request_body

TS=$(date -u "+%a, %d %b %Y %T GMT")

echo "\nHeaders to include in your request:\n"
echo "Date: $TS"

signstring="date: $TS"
signstring="$signstring"
header="date"

if [ ! -z "$request" ]
then
signstring="$signstring\n$request"
header="$header request-line"
fi

if [ ! -z "$hostname" ]
then
signstring="$signstring\nhost: $hostname"
header="$header host"
fi

if [ ! -z "$request_body" ]
then
body_digest=$(printf "$request_body" | openssl dgst -sha256 -binary | openssl enc -base64 -A)
echo "Digest: SHA-256=$body_digest"
header="$header digest"
signstring="$signstring\ndigest: SHA-256=$body_digest"
fi

HMAC=$(printf "$signstring" | openssl dgst -sha256 -hmac $token -binary | openssl enc -base64 -A)
authorization="hmac username=\"$username\", algorithm=\"hmac-sha256\", headers=\"$header\", signature=\"$HMAC\""

echo "Authorization: $authorization"

That’s all I want to cover today, thank you.