S3 Pre-signed URLs can be used to provide a temporary 3rd party access to private objects in S3 buckets. For example non-public files on a file sharing site can only be made available to the approved users with one-off URLs that expire after 10 minutes. Here we offer a simple demo for testing the concept.

The demo consists of a number of parts:

Preparation

  1. Spin up Amazon Linux EC2 instance
    • Assign Public IP or Elasitc IP address, e.g. IP.AD.DR.ES
    • Assign EC2 IAM Role, e.g. s3sign-role
  2. Create S3 Bucket, e.g. s3sign-bucket
  3. The instance needs at least GetObject privilege on the objects in the bucket. A minimal IAM Policy for the s3sign-role should therefore be something like:
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": [ "s3:Get*" ],
          "Resource": [ "arn:aws:s3:::s3sign-bucket/*" ],
          "Effect": "Allow"
        }
      ]
    }

    In reality you will probably want to grant some more privileges: s3:List*, s3:Get*, s3:PutObject* and s3:DeleteObject* probably.

    Also don’t forget to update the bucket name in Resource block if it’s different from s3sign-bucket!

Installation

Refer to the README in the GitHub repository for details.

CloudFormation way

Alternatively instead of following the manual steps above you can use the provided CloudFormation template s3-sign-demo.json that sets us the environment the same way. In the Outputs tab in CloudFormation console you’ll find the IP address of the instance.

Testing

Upload some files and images to s3sign-bucket with Private ACL and browse to http://IP.AD.DR.ES/index.php, where IP.AD.DR.ES is obviously the IP address of your EC2 instance. You should see a page like this:

pre-signed-url-demo

Sure, it’s not the epitome of a contemporary web design but it works 🙂

In the web page you’ll find a table with two URLs

  1. Unsigned URL which is a plain HTTPS URL like https://s3-ap-southeast-2.amazonaws.com/s3sign2-bucket-hchq3nwuo8ns/s3-sign-demo.json
    Most likely you’ll get an Access Denied error when clicking that URL – the file is intentionally not publicly accessible.
  2. Signed URL which is a bit more interesting. It starts with the same URL as above with is followed by a long string with a lot of parameters: https://s3.ap-southeast-2.amazonaws.com/s3sign2-bucket-hchq3nwuo8ns/s3-sign-demo.json?X-Amz-Security-Token=FQ…&X-Amz-Credential=ASIAJF3BXG…&X-Amz-Date=20170125T044127Z&X-Amz-Expires=60&X-Amz-Signature=24db05…

Some parameters in that long string denote the signature validity (X-Amz-Date, X-Amz-Expires) while the rest provide the cryptographic signature (X-Amz-Security-Token, X-Amz-Credential, X-Amz-Signature, etc). Don’t worry about the details – there are ready made library functions that help you create the URL.

The Code

For completeness here is the code that generates the Signed URL. Check out the full PHP script in the GitHub project.

require "aws.phar";

$s3 = new Aws\S3\S3Client([
 'version' => '2006-03-01',
 'region' => $region,
]);

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

$request = $s3->createPresignedRequest($cmd, "+1 minute");

$signed_url = (string) $request->getUri();

That’s all 🙂