6

I am generating a Pre Signed URL to allow users to download files from an S3 Bucket. I am generating the URL's via the PHP SDK using the following code:

public static function get_content_link( $bucket, $key ) {

    //check response code from AWS
    require_once 'aws/aws-autoloader.php';

    $s3 = new Aws\S3\S3Client([
        'version' => 'latest',
        'region'  => 'eu-west-1',
        'credentials' => [
            'key'    => 'MY-KEY',
            'secret' => 'MY-SECRET',
        ],
    ]);

    $cmd = $s3->getCommand('GetObject', [
        'Bucket' => $bucket,
        'Key'    => $key
    ]);

    $request = $s3->createPresignedRequest($cmd, '+500 minutes');

    // Get the actual presigned-url
    $presignedUrl = (string) $request->getUri();

    return $presignedUrl;
}

The URLs are being returned as expected, for example:

https://s3-eu-west-1.amazonaws.com/MY-BUCKET-NAME/product/3166_1480009917388.mov?x-amz-content-sha256=unsigned-payload&x-amz-algorithm=aws4-hmac-sha256&x-amz-credential=akiaiqrmkn276hcpjkaq%2f20161127%2feu-west-1%2fs3%2faws4_request&x-amz-date=20161127t145603z&x-amz-signedheaders=host&x-amz-expires=30000&x-amz-signature=98eaef504f053ca56908ac49c6539c4a8b8e250d7d3a4a12460f4a806ec41c19

When I try to open any of the returned links in the browser I am getting an access denied error from S3:

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId>A37839BB23186F72</RequestId>
  <HostId>
yvKTN+CN1TTNk2tqoxxm3MPOGTUSMaRYtbbEFeCzGP7ou5IYf37Z9uBESwUQWDIUR1GUuPbZyuM=
  </HostId>
</Error>

The files that I want to provide access to are in a bucket which contains folders allowing public access, by the folder I am trying to access is private (called /product/). Our bucket policy looks like this:

{
        "Version": "2008-10-17",
        "Statement": [
            {
                "Sid": "AllowPublicReadProxies",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "*"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::MY-BUCKET-NAME/proxies*"
            },
            {
                "Sid": "AllowPublicReadThumbs",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "*"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::MY-BUCKET-NAME/thumbs*"
            }
        ]
    }

It is my understanding that the purpose of creating a Pre Signed URL is to allow unauthenticated users temporary access to protected files without having to modify the bucket or folder permissions.

Has anyone got any ideas as to what I have missed or done wrong?

Thanks!

8
  • 2
    Where did you grant the IAM user associated with this AWS Access Key Id the necessary permission to perform s3:GetObject? Commented Nov 27, 2016 at 17:44
  • 1
    Can you try generating the pre-signed URL using the AWS Command-Line Interface (CLI) just to confirm that permissions work? The command to use is aws s3 presign. Then, compare the URLs to ensure that all elements match (but the values will differ). Your 'expires' time seems incorrect in the URL you provided. The pre-signed URL does not rely on bucket policies -- it is an alternate way of providing access. Commented Nov 27, 2016 at 22:14
  • @Michael-sqlbot - I am using a root access key rather than a specific IAM User. Commented Nov 28, 2016 at 10:39
  • 1
    Wait a minute, @JohnRotenstein, "the pre-signed URL does not rely on bucket policies -- it is an alternate way of providing access." To be clear, they don't require bucket policies, but they are still subject to bucket policies (such as a policy denying access, or granting access to the user owning the key). Commented Nov 29, 2016 at 20:25
  • 6
    Wait a minute... how is the entire query string getting coerced to lower case? That's definitely invalid. I can replicate this by converting a valid, working signed URL to all-lowercase. Commented Nov 29, 2016 at 20:32

2 Answers 2

4

Many thanks to @Michael-sqlbot for figuring this out for me.

It turns out I was forcing the entire URL to be lowercase when outputting the pre signed URL to the page, so AWS was returning an Access Denied error due to the fact that the resource technically didn't exist.

Sign up to request clarification or add additional context in comments.

2 Comments

Well, technically, the access denied error was caused by S3 overlooking the lowercase x-amz-* query string parameters, and concluding that you were not actually offering any credentials that it should be trying to validate. You can essentially prove this by changing just one of them to all lowercase. S3 will suddenly complain with a new error message that tells you it's missing. Change them all to lower and that message goes away because S3 assumes it's an anonymous request, no credentials provided.
Yeah, I think the common problem here is the url format or encoding. I encountered a similar problem, but instead of things being lowercase, the url formatting was being lost because I was using curl, but wasn't putting quotes around the url string
1

If you're getting this error, also make sure you are not doing a GET request to the presigned url, instead of a POST request.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.