Skip to content

Commit b71b5f4

Browse files
committed
Restrict amazonaws allowed certificate URLs.
1 parent c01fd52 commit b71b5f4

File tree

1 file changed

+50
-32
lines changed

1 file changed

+50
-32
lines changed

Diff for: django_ses/utils.py

+50-32
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import base64
22
import logging
3+
import re
34
import warnings
45
from builtins import bytes
5-
6-
from django_ses.deprecation import RemovedInDjangoSES20Warning
7-
6+
from urllib.error import URLError
87
from urllib.parse import urlparse
98
from urllib.request import urlopen
10-
from urllib.error import URLError
119

1210
from django.core.exceptions import ImproperlyConfigured
11+
1312
from django_ses import settings
13+
from django_ses.deprecation import RemovedInDjangoSES20Warning
1414

1515
logger = logging.getLogger(__name__)
1616

1717
_CERT_CACHE = {}
1818

19+
SES_REGEX_CERT_URL = re.compile(
20+
"(?i)^https://sns\.[a-z0-9\-]+\.amazonaws\.com(\.cn)?/SimpleNotificationService\-[a-z0-9]+\.pem$"
21+
)
22+
1923

2024
def clear_cert_cache():
2125
"""Clear the certificate cache.
@@ -183,6 +187,17 @@ def _get_cert_url(self):
183187
url_obj = urlparse(cert_url)
184188
for trusted_domain in settings.EVENT_CERT_DOMAINS:
185189
parts = trusted_domain.split(".")
190+
if "amazonaws.com" in trusted_domain:
191+
if not SES_REGEX_CERT_URL.match(cert_url):
192+
if len(parts) < 4:
193+
return None
194+
else:
195+
logger.warning('Possible security risk for: "%s"', cert_url)
196+
logger.warning(
197+
"It is strongly recommended to configure the full domain in EVENT_CERT_DOMAINS. "
198+
"See v3.5.0 release notes for more details."
199+
)
200+
186201
if url_obj.netloc.split(".")[-len(parts) :] == parts:
187202
return cert_url
188203

@@ -196,26 +211,28 @@ def _get_bytes_to_sign(self):
196211

197212
# Depending on the message type the fields to add to the message
198213
# differ so we handle that here.
199-
msg_type = self._data.get('Type')
200-
if msg_type == 'Notification':
214+
msg_type = self._data.get("Type")
215+
if msg_type == "Notification":
201216
fields_to_sign = [
202-
'Message',
203-
'MessageId',
204-
'Subject',
205-
'Timestamp',
206-
'TopicArn',
207-
'Type',
217+
"Message",
218+
"MessageId",
219+
"Subject",
220+
"Timestamp",
221+
"TopicArn",
222+
"Type",
208223
]
209-
elif (msg_type == 'SubscriptionConfirmation' or
210-
msg_type == 'UnsubscribeConfirmation'):
224+
elif (
225+
msg_type == "SubscriptionConfirmation"
226+
or msg_type == "UnsubscribeConfirmation"
227+
):
211228
fields_to_sign = [
212-
'Message',
213-
'MessageId',
214-
'SubscribeURL',
215-
'Timestamp',
216-
'Token',
217-
'TopicArn',
218-
'Type',
229+
"Message",
230+
"MessageId",
231+
"SubscribeURL",
232+
"Timestamp",
233+
"Token",
234+
"TopicArn",
235+
"Type",
219236
]
220237
else:
221238
# Unrecognized type
@@ -237,14 +254,14 @@ def _get_bytes_to_sign(self):
237254

238255
def BounceMessageVerifier(*args, **kwargs):
239256
warnings.warn(
240-
'utils.BounceMessageVerifier is deprecated. It is renamed to EventMessageVerifier.',
257+
"utils.BounceMessageVerifier is deprecated. It is renamed to EventMessageVerifier.",
241258
RemovedInDjangoSES20Warning,
242259
)
243260

244261
# parameter name is renamed from bounce_dict to notification.
245-
if 'bounce_dict' in kwargs:
246-
kwargs['notification'] = kwargs['bounce_dict']
247-
del kwargs['bounce_dict']
262+
if "bounce_dict" in kwargs:
263+
kwargs["notification"] = kwargs["bounce_dict"]
264+
del kwargs["bounce_dict"]
248265

249266
return EventMessageVerifier(*args, **kwargs)
250267

@@ -262,31 +279,32 @@ def verify_bounce_message(msg):
262279
Verify an SES/SNS bounce(event) notification message.
263280
"""
264281
warnings.warn(
265-
'utils.verify_bounce_message is deprecated. It is renamed to verify_event_message.',
282+
"utils.verify_bounce_message is deprecated. It is renamed to verify_event_message.",
266283
RemovedInDjangoSES20Warning,
267284
)
268285
return verify_event_message(msg)
269286

270287

271288
def confirm_sns_subscription(notification):
272289
logger.info(
273-
'Received subscription confirmation: TopicArn: %s',
274-
notification.get('TopicArn'),
290+
"Received subscription confirmation: TopicArn: %s",
291+
notification.get("TopicArn"),
275292
extra={
276-
'notification': notification,
293+
"notification": notification,
277294
},
278295
)
279296

280297
# Get the subscribe url and hit the url to confirm the subscription.
281-
subscribe_url = notification.get('SubscribeURL')
298+
subscribe_url = notification.get("SubscribeURL")
282299
try:
283300
urlopen(subscribe_url).read()
284301
except URLError as e:
285302
# Some kind of error occurred when confirming the request.
286303
logger.error(
287-
'Could not confirm subscription: "%s"', e,
304+
'Could not confirm subscription: "%s"',
305+
e,
288306
extra={
289-
'notification': notification,
307+
"notification": notification,
290308
},
291309
exc_info=True,
292310
)

0 commit comments

Comments
 (0)