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:

S3 bucket with a Private access policy, i.e. non-public

EC2 instance with IAM Role attached

IAM Policy that gives the EC2 instance access to the S3 Bucket through the IAM Role

Some files, images, etc uploaded to the S3 bucket

Simple PHP Script running on the EC2 instance that lists the objects (files) in the bucket and generates signed URLs

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](https://github.com/mludvig/aws-s3sign-demo) 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](images/Pre-signed-URL-demo-Google-Chrome_001.png)

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](https://github.com/mludvig/aws-s3sign-demo).

```php
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 🙂