mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Docs: clean up "securing webhooks"
* "SSL" --> "https" * "authorization" --> "authentication" (e.g., "HTTP basic authentication" -- except when referring specifically to the HTTP "Authorization" header used to send it) * add a sidebar with more details on why it matters
This commit is contained in:
@@ -269,14 +269,14 @@ Set to `True` to ignore these problems and send the email anyway. See
|
|||||||
.. rubric:: WEBHOOK_SECRET
|
.. rubric:: WEBHOOK_SECRET
|
||||||
|
|
||||||
A `'random:random'` shared secret string. Anymail will reject incoming webhook calls
|
A `'random:random'` shared secret string. Anymail will reject incoming webhook calls
|
||||||
from your ESP that don't include this authorization. You can also give a list of
|
from your ESP that don't include this authentication. You can also give a list of
|
||||||
shared secret strings, and Anymail will allow ESP webhook calls that match any of them
|
shared secret strings, and Anymail will allow ESP webhook calls that match any of them
|
||||||
(to facilitate credential rotation). See :ref:`securing-webhooks`.
|
(to facilitate credential rotation). See :ref:`securing-webhooks`.
|
||||||
|
|
||||||
Default is unset, which leaves your webhooks insecure. Anymail
|
Default is unset, which leaves your webhooks insecure. Anymail
|
||||||
will warn if you try to use webhooks without a shared secret.
|
will warn if you try to use webhooks without a shared secret.
|
||||||
|
|
||||||
This is actually implemented using HTTP basic authorization, and the string is
|
This is actually implemented using HTTP basic authentication, and the string is
|
||||||
technically a "username:password" format. But you should *not* use any real
|
technically a "username:password" format. But you should *not* use any real
|
||||||
username or password for this shared secret.
|
username or password for this shared secret.
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,37 @@ Securing webhooks
|
|||||||
If not used carefully, webhooks can create security vulnerabilities
|
If not used carefully, webhooks can create security vulnerabilities
|
||||||
in your Django application.
|
in your Django application.
|
||||||
|
|
||||||
At minimum, you should **use SSL** and a **shared authorization secret**
|
At minimum, you should **use https** and a **shared authentication secret**
|
||||||
for your Anymail webhooks. (Really, for *any* webhooks.)
|
for your Anymail webhooks. (Really, for *any* webhooks.)
|
||||||
|
|
||||||
|
|
||||||
Use SSL
|
.. sidebar:: Does this really matter?
|
||||||
-------
|
|
||||||
|
|
||||||
Your Django site must use SSL, and the webhook URLs you
|
Short answer: yes!
|
||||||
give your ESP should start with "https" (not http).
|
|
||||||
|
Do you allow unauthorized access to your APIs? Would you want
|
||||||
|
someone eavesdropping on API calls? Of course not. Well, a webhook
|
||||||
|
is just another API.
|
||||||
|
|
||||||
|
Think about the data your ESP sends and what your app does with it.
|
||||||
|
If your webhooks aren't secured, an attacker could...
|
||||||
|
|
||||||
|
* accumulate a list of your customers' email addresses
|
||||||
|
* fake bounces and spam reports, so you block valid user emails
|
||||||
|
* see the full contents of email from your users
|
||||||
|
* convincingly forge incoming mail, tricking your app into publishing
|
||||||
|
spam or acting on falsified commands
|
||||||
|
* overwhelm your DB with garbage data (do you store tracking info?
|
||||||
|
incoming attachments?)
|
||||||
|
|
||||||
|
... or worse. Why take a chance?
|
||||||
|
|
||||||
|
|
||||||
|
Use https
|
||||||
|
---------
|
||||||
|
|
||||||
|
For security, your Django site must use https. The webhook URLs you
|
||||||
|
give your ESP need to start with *https* (not *http*).
|
||||||
|
|
||||||
Without https, the data your ESP sends your webhooks is exposed in transit.
|
Without https, the data your ESP sends your webhooks is exposed in transit.
|
||||||
This can include your customers' email addresses, the contents of messages
|
This can include your customers' email addresses, the contents of messages
|
||||||
@@ -22,24 +44,29 @@ you receive through your ESP, the shared secret used to authorize calls
|
|||||||
to your webhooks (described in the next section), and other data you'd
|
to your webhooks (described in the next section), and other data you'd
|
||||||
probably like to keep private.
|
probably like to keep private.
|
||||||
|
|
||||||
Configuring SSL is beyond the scope of Anymail, but there are many good
|
Configuring https is beyond the scope of Anymail, but there are many good
|
||||||
tutorials on the web.
|
tutorials on the web. If you've previously dismissed https as too expensive
|
||||||
|
or too complicated, please take another look. Free https certificates are
|
||||||
|
available from `Let's Encrypt`_, and many hosting providers now offer easy
|
||||||
|
https configuration using Let's Encrypt or their own no-cost option.
|
||||||
|
|
||||||
If you aren't able to use https on your Django site, then you should
|
If you aren't able to use https on your Django site, then you should
|
||||||
not set up your ESP's webhooks.
|
not set up your ESP's webhooks.
|
||||||
|
|
||||||
|
.. _Let's Encrypt: https://letsencrypt.org/
|
||||||
|
|
||||||
|
|
||||||
.. setting:: ANYMAIL_WEBHOOK_SECRET
|
.. setting:: ANYMAIL_WEBHOOK_SECRET
|
||||||
|
|
||||||
Use a shared authorization secret
|
Use a shared authentication secret
|
||||||
---------------------------------
|
----------------------------------
|
||||||
|
|
||||||
A webhook is an ordinary URL---anyone can post anything to it.
|
A webhook is an ordinary URL---anyone can post anything to it.
|
||||||
To avoid receiving random (or malicious) data in your webhook,
|
To avoid receiving random (or malicious) data in your webhook,
|
||||||
you should use a shared random secret that your ESP can present
|
you should use a shared random secret that your ESP can present
|
||||||
with webhook data, to prove the post is coming from your ESP.
|
with webhook data, to prove the post is coming from your ESP.
|
||||||
|
|
||||||
Most ESPs recommend using HTTP basic authorization as this shared
|
Most ESPs recommend using HTTP basic authentication as this shared
|
||||||
secret. Anymail includes support for this, via the
|
secret. Anymail includes support for this, via the
|
||||||
:setting:`!ANYMAIL_WEBHOOK_SECRET` setting.
|
:setting:`!ANYMAIL_WEBHOOK_SECRET` setting.
|
||||||
Basic usage is covered in the
|
Basic usage is covered in the
|
||||||
@@ -53,8 +80,8 @@ This will result in an HTTP 400 response, without further processing
|
|||||||
the data or calling your signal receiver function.
|
the data or calling your signal receiver function.
|
||||||
|
|
||||||
In addition to a single "random:random" string, you can give a list
|
In addition to a single "random:random" string, you can give a list
|
||||||
of authorization strings. Anymail will permit webhook calls that match
|
of authentication strings. Anymail will permit webhook calls that match
|
||||||
any of the authorization strings:
|
any of the authentication strings:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -66,14 +93,14 @@ any of the authorization strings:
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
This facilitates credential rotation: first, append a new authorization
|
This facilitates credential rotation: first, append a new authentication
|
||||||
string to the list, and deploy your Django site. Then, update the webhook
|
string to the list, and deploy your Django site. Then, update the webhook
|
||||||
URLs at your ESP to use the new authorization. Finally, remove the old
|
URLs at your ESP to use the new authentication. Finally, remove the old
|
||||||
(now unused) authorization string from the list and re-deploy.
|
(now unused) authentication string from the list and re-deploy.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
If your webhook URLs don't use https, this shared authorization
|
If your webhook URLs don't use https, this shared authentication
|
||||||
secret won't stay secret, defeating its purpose.
|
secret won't stay secret, defeating its purpose.
|
||||||
|
|
||||||
|
|
||||||
@@ -105,5 +132,7 @@ For example, you might consider:
|
|||||||
* Configuring your firewall to reject webhook calls that come from
|
* Configuring your firewall to reject webhook calls that come from
|
||||||
somewhere other than your ESP's documented IP addresses (if your ESP
|
somewhere other than your ESP's documented IP addresses (if your ESP
|
||||||
provides this information)
|
provides this information)
|
||||||
|
* Rate-limiting webhook calls in your web server or using something
|
||||||
|
like :pypi:`django-ratelimit`
|
||||||
|
|
||||||
But you should start with using SSL and a random shared secret via HTTP auth.
|
But you should start with using https and a random shared secret via HTTP auth.
|
||||||
|
|||||||
Reference in New Issue
Block a user