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
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
Create S3 Bucket, e.g. s3sign-bucket
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:

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 🙂