Search code examples
djangoamazon-s3boto3django-storage

Django + S3 Signature expiration


Should my django-storage instance be storing the S3 signature in the database along with the image name?

If so how do you regenerate a new key on each request assuming the old request expiration is already past?

models.py

def user_directory_path(self, filename):
    return 'profile/user_{0}/images/profile/{1}'.format(self.user.id, filename)
    ...
    ...
    user_image = models.ImageField(upload_to=user_directory_path, storage=PrivateMediaStorage(), blank=True)

storage_backends.py

class PrivateMediaStorage(S3Boto3Storage):
    location = 'private'
    default_acl = 'private'
    file_overwrite = False
    custom_domain = False

Sorry new to Django, want to make sure my logic is correct.


Solution

  • No you don't want it to be storing that kind of data because then you'll lose access to the file. I've had this exact problem in the past.

    The signature should be added by the system at the time the file needs to be accessed.

    These are my settings, which work without saving the signature, for a private S3 storage bucket;

    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
    
    S3_BUCKET = env('S3_BUCKET', 'my_bucket')
    S3_SUB_BUCKET = env('S3_SUB_BUCKET', 'dev')
    S3_REGION = env('S3_REGION', 'eu-west-2')
    AWS_REGION = env('AWS_REGION', 'eu-west-2')
    S3_USE_SIGV4 = True
    
    AWS_S3_PATH_SEPARATOR = '/'
    AWS_STORAGE_BUCKET_NAME = S3_BUCKET
    AWS_S3_HOST = f'{S3_BUCKET}.s3.{S3_REGION}.amazonaws.com'
    AWS_S3_SIGNATURE_VERSION = 's3v4'
    AWS_S3_ADDRESSING_STYLE = 'virtual'
    AWS_DEFAULT_ACL = 'private'
    AWS_LOCATION = S3_SUB_BUCKET
    AWS_S3_FILE_OVERWRITE = False
    

    Some of the settings you define in your custom storage class I have in the settings which saves sub-classing.

    When using a file/image field I then just do;

    photo = models.ImageField(
        verbose_name=_('photo'),
        upload_to='photos',
        default=None,
        blank=True,
        null=True,
    )
    

    And on fields where my upload_to is a callable I have these methods;

    def uuid():
        """ Generate a UUID """
        return urlsafe_b64encode(uuid4().bytes).decode("ascii").rstrip("=")
    
    
    def upload_path(instance, filename):
        """ Upload to path with UUID """
        return "{uuid}/{file}".format(uuid=uuid(), file=filename)
    

    The docs for things like AWS_S3_ADDRESSING_STYLE aren't great, but that's one I missed for a while & it lead to things being stored in unexpected locations within my buckets. I think I was seeing <bucket>/<bucket>/<path_to_file type issues without that setting.