HMAC Authentication And Integrity Verification¶
This middleware validates a digital signature computed using the content of an HTTP request and a shared secret that is sent to the proxy using the Authorization
or Proxy-Authorization
header.
It ensures:
- The identity of the sender (Authentication): If the signature is validated by the proxy, it means that the sender actually owns the shared secret. As a consequence, the sender's identity is considered to be proven.
- The integrity of the request: As the signature is based on a subset of the HTTP request, it means that if the signature is validated by the proxy, the request used to generate the signature has not been modified between the sender and the proxy. This middleware also allows validating the content integrity using the Digest header.
This middleware is based on the HTTP Signature Draft.
Authentication Mechanism¶
The sender and proxy share a common secret identified by a keyId
. How the sender gets the secret and keeps it safe is out of scope of this documentation.
Crafting the Authorization Header¶
To authenticate a request, the sender must provide an Authorization
or ProxyAuthorization
header fulfilling the HMAC authentication scheme.
This header carries a set of parameters:
Authorization: Hmac keyId="secret-id-1",algorithm="hmac-sha256",headers="(request-target) (created) (expires) host x-example",signature="c29tZXNpZ25hdHVyZQ==",created="1584453022",expires="1584453032"
Parameter | Description | Example |
---|---|---|
keyId |
Identifier of the key being used by the sender to build the signature | keyId="secret-key-1" |
algorithm |
Algorithm used to generate the signature. Supported values are hmac-sha1 , hmac-sha256 , hmac-sha384 and hmac-sha512 . |
algorithm="hmac-sha512" |
headers |
List of headers to use in order to build the signature string. Each item must be lowercase. |
headers="host content-type" |
signature |
Digital Signature of the request. See computing the signature. | signature="c29tZXNpZ25hdHVyZQ==" |
created |
Unix timestamp of the signature creation. | created="1574453022" |
expires |
Unix timestamp of the signature expiration. | expires="1574453022" |
Time sensitivity
If the created
timestamp is in the future or the expires
timestamp is in the past, the middleware will reject the request.
This behaviour makes using this middleware sensitive to clock skew between the client and the server.
Make sure that your client and your server have their clocks synchronized.
Computing the Signature¶
The signature is the base64-encoded value of the result of an HMAC signature algorithm computed with a signature string
and the sender's secret key
.
For example:
signature=base64(HMAC(signatureString, secret))
Crafting the Signature String¶
A signed HTTP request needs to be tolerant of some trivial alterations during transmission as it goes through gateways, proxies and other entities. As a consequence, signing the whole request is not an option as a single header modification could result in an invalid signature.
To avoid this problem, this middleware builds the signature string
from a subset of header values defined by the sender with the headers
parameter of the authorization header.
To build the signature string, the client must take the values of each header specified by the headers
parameter in the order they appear, then apply the following logic to each of them:
- If the header is a special header, then evaluate its value according to the special headers values section
- If the header is not a special header, then append the lowercase header name followed with an ASCII colon
:
, an ASCII space ` ` and the header value. If the header has multiple values then append those values separated by an ASCII comma,
and an ASCII space ` ` - If value is not the last value then append an ASCII newline
\n
. The signature string MUST NOT include a trailing ASCII newline
All headers values are trimmed from their whitespaces
Special Header Values¶
By design, all the information of an HTTP request is not available as headers. However, it makes sense to secure the request using them.
To allow this, the headers
parameter accepts special header names that can be used.
Value | Description | Signature String Example |
---|---|---|
(request-target) |
Obtained by concatenating the lowercase :method , an ASCII space, and the :path pseudo-headers (as specified in HTTP/2). |
(request-target): get /api/V1/resource?query=foo |
(created) |
Value of the authorization header created parameter. |
(created): 1584453022 |
(expires) |
Value of the authorization header expires parameter. |
(expires): 1584453082 |
Their evaluated value is obtained by appending the special header name with an ASCII colon :
an ASCII space ` ` then the designated value.
(created): 1929494939
(request-target): get /foo/bar
Signature String Example¶
Here is an example with the authorization header parameters set:
headers="(request-target) (created) (expires) host x-example x-emptyheader cache-control"
created="1584466921"
expires="1584466931"
GET /foo HTTP/1.1
Host: example.org
X-Example: Example header
with some whitespace.
X-EmptyHeader:
X-NotIncluded: always
Cache-Control: max-age=60
Cache-Control: must-revalidate
(request-target): get /foo
(created): 1584466921
(expires): 1584466931
host: example.org
x-example: Example header with some whitespace.
x-emptyheader:
cache-control: max-age=60, must-revalidate
Enforced Headers¶
It is possible to configure the middleware to enforce a minimum set of headers to create the signature string. This means that any request that does not have the enforced headers in its signature is systematically rejected.
This option also configures the headers list returned when initiating the authentication.
It defaults to (request-target) (created) (expires)
.
Always enforce (created) and (expires)
The created
and expires
header parameters protect against replay attacks.
To make sure that their value is not modified during transport, it is highly recommended to always include those parameters values in the signature using the (created)
and (expired)
special headers value.
To do so, it is recommended to always configure the middleware to enforce (created)
and (expires)
.
Initiating the Authentication¶
The authentication can be initiated by the proxy. A 401 Unauthorized
response is returned with a WWW-Authenticate
header indicating to use the Hmac
authentication scheme.
WWW-Authenticate: Hmac headers="(request-target) (created) (expires) host x-example"
This header indicates that the sender needs to provide an Authorization header that fulfills the Hmac
authentication schemes. It also indicates a list of headers that have to be included in the signature using the headers
parameter.
Enforced headers
The list of headers carried in the WWW-Authenticate
header is the list of enforced headers indicated in the middleware configuration.
Validating Request Body Integrity¶
It is possible to make sure that the body of the incoming request has not been altered during transmission by including the digest header in the signature string.
This middleware, by default, validates the digest sum of the body, if there is one.
Only SHA-256 and SHA-512 checksums are supported for checksum computation
Potential CPU and memory usage
Valdiating the digest makes the middleware read the request body and computes a checksum for it. As a consequence it can cause high memory and CPU usage on proxies.
To disable this feature and only perform authentication, set the validateDigest
option to false
in the middleware configuration.
Authentication Source¶
Before configuring an HMAC middleware, an Authentication Source must be defined in the static configuration.
Below is an example of a minimal HMAC Authentication Source that can be added to a static configuration:
[authSources]
[authSources.secure-users]
[authSources.secure-users.hmac]
[[authSources.secure-users.hmac.inline]]
id = "secret-key"
key = "secret"
[[authSources.secure-users.hmac.inline]]
id = "secret-key-2"
key = "secret"
authSources:
secure-users:
hmac:
inline:
- id: "secret-key"
key: "secret"
Authentication Source Options¶
inline
¶
Required, Default=None
A static set of secret keys to be used by HMAC middleware.
[authSources]
[authSources.secure-users]
[authSources.secure-users.hmac]
[[authSources.secure-users.hmac.inline]]
id = "secret-key"
key = "secret"
[[authSources.secure-users.hmac.inline]]
id = "secret-key-2"
key = "secret"
authSources:
secure-users:
hmac:
inline:
- id: "secret-key"
key: "secret"
HMAC Middleware¶
After declaring an HMAC Authentication Source in the static configuration of the cluster, HMAC middleware can be added to routers in the dynamic configuration:
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.source=secure-users"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmacAuth:
source: secure-users
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
"labels": {
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source": "secure-users"
}
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
[http.middlewares]
[http.middlewares.hmac-auth.plugin.hmacAuth]
source = secure-users
http:
middlewares:
hmac-auth:
plugin:
hmacAuth:
source: secure-users
Middleware Options¶
source
¶
Required, Default=""
The source
option should contain the name of the Authentication Source used by the middleware.
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.source=secure-users"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmacAuth:
source: secure-users
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
"labels": {
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source": "secure-users"
}
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
[http.middlewares]
[http.middlewares.hmac-auth.plugin.hmacAuth]
source = secure-users
http:
middlewares:
hmac-auth:
plugin:
hmacAuth:
source: secure-users
validateDigest
¶
Optional, Default=true
validateDigest
determines whether the middleware should validate the digest sum of the request body.
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.validateDigest=true"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmacAuth:
validateDigest: true
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest=true"
"labels": {
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest": "true"
}
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest=true"
[http.middlewares]
[http.middlewares.hmac-auth.plugin.hmacAuth]
validateDigest = true
http:
middlewares:
hmac-auth:
plugin:
hmacAuth:
validateDigest: true
enforcedHeaders
¶
Optional, Default=None
enforcedHeaders
is a set of headers that must be included in the computation of the signature of the request.
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.enforcedHeaders=(request-target),(created),(expires),host,digest"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmacAuth:
enforcedHeaders:
- (request-target)
- (created)
- (expires)
- host
- digest
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders=(request-target),(created),(expires),host,digest"
"labels": {
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders": "(request-target),(created),(expires),host,digest"
}
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders=(request-target),(created),(expires),host,digest"
[http.middlewares]
[http.middlewares.hmac-auth.plugin.hmacAuth]
enforcedHeaders = [
"(request-target)",
"(created)",
"(expires)",
"host",
"digest"
]
http:
middlewares:
hmac-auth:
plugin:
hmacAuth:
enforcedHeaders:
- (request-target)
- (created)
- (expires)
- host
- digest
Advanced Configuration Example¶
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.source=secure-users"
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.validateDigest=true"
- "traefik.http.middlewares.hmac-auth.plugin.hmacauth.enforcedHeaders=(request-target),(created),(expires),host,digest"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmacAuth:
source: secure-users
validateDigest: true
enforcedHeaders:
- (request-target)
- (created)
- (expires)
- host
- digest
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest=true"
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders=(request-target),(created),(expires),host,digest"
"labels": {
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source": "secure-users",
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest": "true",
"traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders": "(request-target),(created),(expires),host,digest"
}
labels:
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.source=secure-users"
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.validateDigest=true"
- "traefik.http.middlewares.hmac-auth.plugin.hmacAuth.enforcedHeaders=(request-target),(created),(expires),host,digest"
[http.middlewares]
[http.middlewares.hmac-auth.plugin.hmacAuth]
source = secure-users
validateDigest = true
enforcedHeaders = [
"(request-target)",
"(created)",
"(expires)",
"host",
"digest"
]
http:
middlewares:
hmac-auth:
plugin:
hmacAuth:
source: secure-users
validateDigest: true
enforcedHeaders:
- (request-target)
- (created)
- (expires)
- host
- digest