Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S3Boto3Storage: use UNSIGNED signature version for public urls #569

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 10 additions & 5 deletions docs/backends/amazon-S3.rst
Expand Up @@ -67,11 +67,6 @@ Available are numerous settings. It should be especially noted the following:
'CacheControl': 'max-age=86400',
}

``AWS_QUERYSTRING_AUTH`` (optional; default is ``True``)
Setting ``AWS_QUERYSTRING_AUTH`` to ``False`` to remove query parameter
authentication from generated URLs. This can be useful if your S3 buckets
are public.

``AWS_S3_MAX_MEMORY_SIZE`` (optional; default is ``0`` - do not roll over)
The maximum amount of memory a file can take up before being rolled over
into a temporary file on disk.
Expand Down Expand Up @@ -117,6 +112,16 @@ Available are numerous settings. It should be especially noted the following:
``AWS_S3_CALLING_FORMAT`` (optional: default is ``SubdomainCallingFormat()``)
Defines the S3 calling format to use to connect to the static bucket.

``AWS_QUERYSTRING_AUTH`` (optional; default is ``True``)
If set to ``False`` then on Boto3 this will use an UNSIGNED signature version
which speeds up responses times and requires a lower level of access. The setting
``AWS_S3_SIGNATURE_VERSION`` is ignored in this case.

On boto set ``AWS_QUERYSTRING_AUTH`` to ``False`` to remove query parameter
authentication from generated URLs. This can be useful if your S3 buckets
are public.


``AWS_S3_SIGNATURE_VERSION`` (optional - boto3 only)

All AWS regions support v4 of the signing protocol. To use it set this to ``'s3v4'``. It is recommended
Expand Down
33 changes: 6 additions & 27 deletions storages/backends/s3boto3.py
Expand Up @@ -15,12 +15,12 @@
filepath_to_uri, force_bytes, force_text, smart_text,
)
from django.utils.six import BytesIO
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.timezone import is_naive, localtime

from storages.utils import check_location, lookup_env, safe_join, setting

try:
import botocore
import boto3.session
from boto3 import __version__ as boto3_version
from botocore.client import Config
Expand Down Expand Up @@ -261,8 +261,10 @@ def __init__(self, acl=None, bucket=None, **settings):
self.security_token = self._get_security_token()

if not self.config:
self.config = Config(s3={'addressing_style': self.addressing_style},
signature_version=self.signature_version)
self.config = Config(
s3={'addressing_style': self.addressing_style},
signature_version=botocore.UNSIGNED if not self.querystring_auth else self.signature_version,
)

# warn about upcoming change in default AWS_DEFAULT_ACL setting
if not hasattr(django_settings, 'AWS_DEFAULT_ACL'):
Expand Down Expand Up @@ -573,27 +575,6 @@ def modified_time(self, name):
mtime = self.get_modified_time(name)
return mtime if is_naive(mtime) else localtime(mtime).replace(tzinfo=None)

def _strip_signing_parameters(self, url):
# Boto3 does not currently support generating URLs that are unsigned. Instead we
# take the signed URLs and strip any querystring params related to signing and expiration.
# Note that this may end up with URLs that are still invalid, especially if params are
# passed in that only work with signed URLs, e.g. response header params.
# The code attempts to strip all query parameters that match names of known parameters
# from v2 and v4 signatures, regardless of the actual signature version used.
split_url = urlparse.urlsplit(url)
qs = urlparse.parse_qsl(split_url.query, keep_blank_values=True)
blacklist = {
'x-amz-algorithm', 'x-amz-credential', 'x-amz-date',
'x-amz-expires', 'x-amz-signedheaders', 'x-amz-signature',
'x-amz-security-token', 'awsaccesskeyid', 'expires', 'signature',
}
filtered_qs = ((key, val) for key, val in qs if key.lower() not in blacklist)
# Note: Parameters that did not have a value in the original query string will have
# an '=' sign appended to it, e.g ?foo&bar becomes ?foo=&bar=
joined_qs = ('='.join(keyval) for keyval in filtered_qs)
split_url = split_url._replace(query="&".join(joined_qs))
return split_url.geturl()

def url(self, name, parameters=None, expire=None):
# Preserve the trailing slash after normalizing the path.
# TODO: Handle force_http=not self.secure_urls like in s3boto
Expand All @@ -609,9 +590,7 @@ def url(self, name, parameters=None, expire=None):
params['Key'] = self._encode_name(name)
url = self.bucket.meta.client.generate_presigned_url('get_object', Params=params,
ExpiresIn=expire)
if self.querystring_auth:
return url
return self._strip_signing_parameters(url)
return url

def get_available_name(self, name, max_length=None):
"""Overwrite existing file with the same name."""
Expand Down
7 changes: 0 additions & 7 deletions tests/test_s3boto3.py
Expand Up @@ -518,13 +518,6 @@ def test_special_characters(self):
parsed_url = urlparse.urlparse(url)
self.assertEqual(parsed_url.path, "/%C3%A3l%C3%B6h%C3%A2.jpg")

def test_strip_signing_parameters(self):
expected = 'http://bucket.s3-aws-region.amazonaws.com/foo/bar'
self.assertEqual(self.storage._strip_signing_parameters(
'%s?X-Amz-Date=12345678&X-Amz-Signature=Signature' % expected), expected)
self.assertEqual(self.storage._strip_signing_parameters(
'%s?expires=12345678&signature=Signature' % expected), expected)

@skipIf(threading is None, 'Test requires threading')
def test_connection_threading(self):
connections = []
Expand Down