Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS S3 Presigned Request Cache

I want to store user profile pictures in an S3 bucket, but keep these images private. In order to do this, I am creating a presigned url whenever the image is required. However, this creates a unique url each time which means the image will never be cached by the browser and I'll end up paying a lot more in GET requests.

Here's an example of my code to generate the url, I'm using Laravel:

$s3 = \Storage::disk('s3');
$client = $s3->getDriver()->getAdapter()->getClient();
$expiry = new \DateTime('2017-07-25');

$command = $client->getCommand('GetObject', [
    'Bucket' => \Config::get('filesystems.disks.s3.bucket'),
    'Key'    => $key
]);

$request = $client->createPresignedRequest($command, $expiry);

return (string) $request->getUri();

I thought that by specifying a datetime rather a unit of time that it would create the same url but it actually adds the number of seconds remaining to the url, here's an example:

xxxx.s3.eu-west-2.amazonaws.com/profile-pics/92323.png?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AXXXXXXXXXXX%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20170720T112123Z&X-Amz-SignedHeaders=host&X-Amz-Expires=391117&X-Amz-Signature=XXXXXXXXX

Is it possible to generate a repeatable presigned request url so that an image may be cached by the users browser?

like image 402
ezero Avatar asked Jul 20 '17 11:07

ezero


People also ask

Does AWS S3 cache?

By default, CloudFront caches a response from Amazon S3 for 24 hours (Default TTL of 86,400 seconds). If your request lands at an edge location that served the Amazon S3 response within 24 hours, then CloudFront uses the cached response. This happens even if you updated the content in Amazon S3.

How many times can a Presigned URL be used?

It grants restricted access — only one of GET or PUT is allowed for a single URL. Only to a single object — each pre-signed URL corresponds to one object. With a time-constrained — the URL expires after a set timeout.

Why is my Presigned URL for an Amazon S3 bucket expiring before the expiration time that I specified?

Because presigned URLs grant Amazon S3 bucket access to whoever has the URL, it's a best practice to protect them appropriately. If you created a presigned URL using a temporary token, then the URL expires when the token expires. This is true even if the URL was created with a later expiration time.

How long is S3 Presigned URL?

In the Amazon S3 console, the maximum expiration time for a presigned URL is 12 hours from the time of creation. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/ .


1 Answers

Maybe a late reply, but I'll add my approach for the benefit of people reading this in future.

To force the browser cache to kick in, it's important to generate same exact url every time until you specifically want the browser to reload content from the server. Unfortunately the presigner provided in the sdk, relies on current timestamp leading to a new url every time.

This example is in Java but it can easily be extended to other languages

The GetObjectRequest builder(used to create the presigned url) allows overriding configuration. We can supply a custom signer to modify its behaviour

AwsRequestOverrideConfiguration.builder()
    .signer(new CustomAwsS3V4Signer())
    .credentialsProvider(<You may need to provide a custom credential provider 
here>)))
.build())

GetObjectRequest getObjectRequest =
    GetObjectRequest.builder()
            .bucket(getUserBucket())
            .key(key)
            .responseCacheControl("max-age="+(TimeUnit.DAYS.toSeconds(7)+ defaultIfNull(version,0L)))
            .overrideConfiguration(overrideConfig)
            .build();

public class CustomAwsS3V4Signer implements Presigner, Signer
{
    private final AwsS3V4Signer awsSigner;

    public CustomAwsS3V4Signer()
    {
        awsSigner = AwsS3V4Signer.create();
    }

@Override
public SdkHttpFullRequest presign(SdkHttpFullRequest request, ExecutionAttributes executionAttributes)
{
    Instant baselineInstant = Instant.now().truncatedTo(ChronoUnit.DAYS);

    executionAttributes.putAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION,
            baselineInstant.plus(3, ChronoUnit.DAYS));

Here we override the signing clock to simulate a fixed time which ultimately results in consistent expiry and signature in the url until a certain date in future:

    Aws4PresignerParams.Builder builder = Aws4PresignerParams.builder()
            .signingClockOverride(Clock.fixed(baselineInstant, ZoneId.of("UTC")));

    Aws4PresignerParams signingParams =
            extractPresignerParams(builder, executionAttributes).build();

    return awsSigner.presign(request, signingParams);
    }
}

More details are available here:

https://murf.ai/resources/creating-cache-friendly-presigned-s3-urls-using-v4signer-q1bbqgk

like image 82
Aragorn Avatar answered Oct 10 '22 16:10

Aragorn