Question

I'm trying to serve time-limited links to private content on a Cloudfront-enabled Amazon S3 bucket.

I would like to be able to use the AWS PHP API

$credentials = array("key" => $variables->strAmazonAccessKey, "secret" => $variables->strAmazonSecretKey);
$s3 = new AmazonS3($credentials);
$cdn = new AmazonCloudFront($credentials);

$cdn->set_keypair_id($variables->cdn_keypair_id);
$cdn->set_private_key($variables->cdn_private_key);

$response = $s3->set_object_acl($bucket, $obj, AmazonS3::ACL_OWNER_FULL_CONTROL);
return htmlspecialchars($cdn->get_private_object_url($cloudfront_id, $obj, '1 day'));

But I keep getting this access denied message

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>BD2B4CE946ED67C4</RequestId>
<HostId>
JrBu9+HqhGwzRA4ILFeT2SGyp5nXEY/RrYWQDz2dzdWDIRTgVy2i3Llm460ok99M
</HostId>
</Error>

The cdn_private_key is a string containing the RSA private key which looks sort of like this:

-----BEGIN RSA PRIVATE KEY-----
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlvweljbsdvljkbsadvlkjsd
ldflsjhflasdkjfhlvbdslvahsdlviwuheliuhvlhlv=
-----END RSA PRIVATE KEY-----

I may be doing something wrong there, but I would've expected to get an error about the key or signature instead of an access denied message.

I have also tried manually signing using the following method, but get the same error, only with different HostId and RequestId:

$accessId = $variables->cdn_keypair_id;
$priv_key = $variables->cdn_private_key;
$resource = 'https://'. $cloudfront_id .'/' . $obj;

$expires = time() + 3600*24; 
$to_sign = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';     

$signature = '*Signature will go here*'; 
$pkeyid = openssl_get_privatekey($priv_key); 
if (openssl_sign( $to_sign, $signature, $pkeyid, OPENSSL_ALGO_SHA1)) { 
    $signature = urlencode( base64_encode( $signature ) ); 
} 
return ($resource.'?Key-Pair-Id='.$accessId.'&Expires='.$expires.'&Signature='.$signature); 
Was it helpful?

Solution

I've eventually solved the problem.

It seems that Amazon aren't very clear about this ... hidden deep in the bowels of AWS documentation you are instructed to set the bucket permissions on the S3 bucket to allow CloudFront access to it.

Further confusion ensues when you have to set the Principal property on the policy, as it suggests you need to get the Canonical User ID. However, this is NOT the Canonical User ID for your AWS account found on the Security Credentials page .. it is instead found in the "Origin Access Identity" link on the CloudFront console.

Here is how to do it ....

First create/obtain the CloudFront Origin Access Identity like this:-

$oai_id = $cdn->list_oais()->body->CloudFrontOriginAccessIdentitySummary->Id;
if(!$oai_id)
{
    $cdn->create_oai('SOME_IDENTIFIER');
    $oai_id = $cdn->list_oais()->body->CloudFrontOriginAccessIdentitySummary->Id;
}

Now apply the policy to the bucket to allow CloudFront access:-

$cuid = $cdn->get_oai($oai_id)->body->S3CanonicalUserId;
$policy = new CFPolicy($s3, array(
    'Statement' => array(
        array( // Statement #1
            'Sid' => 'AddPerm',
            'Effect' => 'Allow',
            'Principal' => array(
                'CanonicalUser' => "$cuid"
            ),
            'Action' => array('s3:GetObject'),
            'Resource' => array('arn:aws:s3:::'.$bucket.'/*')
        )
    )
));
// Set the bucket policy
$response = $s3->set_bucket_policy($bucket, $policy);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top