falcon-signed-requests

falcon-signed-requests


License
MIT
Install
pip install falcon-signed-requests==1.0.0

Documentation

Falcon Middleware: Authentic Signed Requests

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.

Installation

$ pip install falcon-signed-requests

Usage

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.

How it Works

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:

  1. generating a UUID1 for the request (which includes a timestamp)
  2. concatenating the UUID with the request body
  3. 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.

Configuration

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
}

About Falcon

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.