The falcon-signed-requests
package provides a middleware component
that enables UUID and Signature headers to be used for determining the
authenticity of a request from a trusted source.
$ pip install falcon-signed-requests
The AuthenticRequestMiddleware
middleware class examines each incoming request
and verifies the X-{name}-SIGNATURE
and X-{name}-UUID
headers to determine
if the signature is authentic and the UUID (v1) has not expired or been previously
used.
Getting Started:
- Create a Redis instance (if using the request UUID for replay protection)
- Create an instance of
AuthenticRequestMiddleware
using the configuration and redis instance - Pass the instance to the
falcon.API()
initializer:
from redis import Redis
from falcon_signed_requests import AuthenticRequestMiddleware
redis = Redis()
config = {
"secret": APP_SECRET
}
app = falcon.API(
middleware=[
AuthenticRequestMiddleware(config, redis)
]
)
If validation fails an instance of falcon.HTTPForbidden
is raised.
When a request contains both the headers X-{name}-SIGNATURE and X-{name}-UUID (where {name} denotes the configured header name) it signals that this request has been sent from a trusted system (e.g. a trusted cloud service).
If a request has a X-{name}-SIGNATURE header, then the request stream is consumed (as it is read for verification). If the request is deemed authentic, a request.body is provided with the body bytes, the request body is parsed using the media handler in order to provide request.media as well, and request.is_authentic is set to True,
The are headers generated by the requesting system by following the steps:
- generating a UUID1 for the request (which includes a timestamp)
- concatenating the UUID with the request body
- generating a HMAC (SHA256) base64-digest signature
e.g. for the default configuration
import base64
import hashlib
import hmac
import uuid
secret = "<Shared Secret>".encode('utf-8')
body = "Body Bytes".encode('utf-8')
request_id = str(uuid.uuid1()).encode("utf-8")
signature = base64.b64encode(
hmac.new(
key=secret,
msg=request_id + body,
digestmod=hashlib.sha256
).digest()
)
Only the trusted party bearing a shared secret (specified in configuration) can generate the correct signature. These steps are then followed again (within this middleware) in order to generate another signature. If the two signatures match, the request is authentic.
The UUID is used as both a timestamp (for timely expiry) and a nonce (to prevent replay). In combination with the provided signature, these request headers ensure that if the request is intercepted:
- the request body and UUID cannot be modified (it is authentic)
- there is a small time window in which the request can be used (it expires)
- the request cannot be replayed (it is single-use)
If these expiry and replay prevention features are not required, the "is_uuid_required" option can be set to False. In this case when the UUID is not required, step 2. above is also not performed. Additionally Redis is not required (for checking nonces) and can be set to None in the constructor.
If a request is indeed authentic, "{name}-authenticated" is set to True in the request context. Additionally, a "{name}-uuid" field is added if one is not provided in the header. If the request is not authentic, a falcon.HTTPForbidden is raised.
The config dictionary expects the fields:
- secret: the shared secret to use for generating signatures
- header: the name of the header (see above, defaults to "auth")
- expiry: the number of seconds a request is valid for (defaults to 300s, or 5min)
- digest: the digest method to use ("base64" or "hex", defaults to "base64")
- hash: the hashing algorithm to use ("sha256" or "sha1", defaults to "sha256")
- signature_prefix: a prefix to add in front of the signature value (defaults to empty string "")
- nonce_prefix: the prefix to use for nonce key names in redis (defaults to "nonce")
- is_uuid_required: Whether the X-{name}-UUID is included in the check (defaults to True)
e.g. To create a configuration which authenticates Github Webhooks:
{
"secret": SECRET_TOKEN,
"header": "hub",
"signature_prefix": "sha1=",
"hash": "sha1",
"digest": "hex",
"is_uuid_required": False
}
Falcon is a bare-metal Python web framework for building lean and mean cloud APIs and app backends. It encourages the REST architectural style, and tries to do as little as possible while remaining highly effective.