I have the following configuration in cloudfront
Origins
Origin name | Origin domain | Origin path | Origin type | Origin Shield region | Origin access |
---|
bucket-user-files.s3.us-east-2.amazonaws.com | bucket-user-files.s3.us-east-2.amazonaws.com | | S3 | us-east-2 | E29MY3 |
bucket-events.s3.us-east-2.amazonaws.com | bucket-events.s3.us-east-2.amazonaws.com | | S3 | us-east-2 | E2AYU |
in both of them I have activated the Origin Access option: Origin Access Control Settings (recommended)
Behaviors
Precedence | Path pattern | Origin or origin group | Viewer protocol policy | Cache policy name | Origin request policy name | Response headers policy name |
---|
0 | events/* | bucket-events.s3.us-east-2.amazonaws.com | Redirect HTTP to HTTPS | TW_CF_S3_UserFIles | TW_CF_S3_UserFIles_Origin | - |
1 | user-files/* | bucket-user-files.s3.us-east-2.amazonaws.com | Redirect HTTP to HTTPS | TW_CF_S3_UserFIles | TW_CF_S3_UserFIles_Origin | - |
2 | Default (*) | bucket-user-files.s3.us-east-2.amazonaws.com | Redirect HTTP to HTTPS | TW_CF_S3_UserFIles | TW_CF_S3_UserFIles_Origin | - |
- in both I have Viewer option active: HTTP methods allowed: GET, HEAD, OPTIONS -> Cache HTTP mwthods: OPTIONS
- in both in Restrict viewer access, I have it set to yes -> Trusted key groups -> post-files (key group)
- in both I use the same key group in both in Cache key and origin requests I have selected the option "Cache policy and origin request policy".
S3 buckets:
in both buckets I have the same policies only differing in the resource, for their respective bucket.
bucket-user-files
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucket-user-files/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::084135377906:distribution/EV633TPE1OT1W"
}
}
}
]
}
bucket-events
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucket-events/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::084135377906:distribution/EV633TPE1OT1W"
}
}
}
]
}
in both buckets I have the following configuration Block public access (bucket configuration)
Block all public access: Disabled
Not selected: Block public access to buckets and objects granted through new access control lists (ACL)
Not selected : Block public access to buckets and objects granted through any Access Control List (ACL)
selected: Block public access to buckets and objects granted through new bucket policies and public access points
selected: Block public and inter-account access to buckets and objects granted through any public bucket and access point policy.
Access Control List (ACL)
Recipient | Objects | Bucket ACLs |
---|
Bucket owner (your AWS account) | List, Read, Write | Read, Write |
Everyone (public access) | - | - |
Group of authenticated users (anyone with an AWS account) | - | - |
Group S3 log forwarding | - | - |
Cross-Origin Resource Sharing (CORS)
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
now, in my code I have the following
<?php
namespace App\Service\AWS;
use Aws\CloudFront\CloudFrontClient;
use Aws\Exception\AwsException;
use DateInterval;
use DateTime;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
class CloudFront
{
const EXPIRATION_TIME_CLOUDFRONT_URL = 'PT8H';
const CACHE_PUBLIC_URL_TIME = 'P2M';
private CloudFrontClient $cloudFrontClient;
private TagAwareCacheInterface $cache;
private string $keyPath;
private string $keyId;
private string $distributionUrl;
private string $origin;
public function __construct(
CloudFrontClient $cloudFrontClient,
TagAwareCacheInterface $cache,
string $PRIVATE_KEY_ID,
string $PRIVATE_KEY_PATH,
string $DISTRIBUTION_URL,
) {
$this->cloudFrontClient = $cloudFrontClient;
$this->cache = $cache;
$this->keyId = $PRIVATE_KEY_ID;
$this->keyPath = $PRIVATE_KEY_PATH;
$this->distributionUrl = $DISTRIBUTION_URL;
}
public function getSignedUrl(string $origin, string $key, string $ip): string {
$expires = new DateTime();
$expires->add(new DateInterval(self::EXPIRATION_TIME_CLOUDFRONT_URL));
if ($ip == "::1" || $ip == "127.0.0.1") { // for test in local
$ip = file_get_contents('https://ipecho.net/plain');
}
$resource = $this->distributionUrl.$origin."/".$key;
$customPolicy = <<<POLICY
{
"Statement": [
{
"Resource": "{$resource}",
"IpAddress": {"AWS:SourceIp": "{$ip}/32"},
"Condition": {
"DateLessThan": {"AWS:EpochTime": {$expires->getTimestamp()}}
}
}
]
}
POLICY;
try {
return $this->cloudFrontClient->getSignedUrl([
'url' => $resource,
'policy' => $customPolicy,
'private_key' => $this->keyPath,
'key_pair_id' => $this->keyId
]);
} catch (AwsException $e) {
return 'Error: ' . $e->getAwsErrorMessage();
}
}
/**
* @throws \Psr\Cache\InvalidArgumentException
*/
public function getPublicUrl(string $origin, string $key)
{
$itemName = htmlspecialchars($key);
$itemName = str_replace('/','',$itemName);
return $this->cache->get("public_".$itemName."_file",
function (ItemInterface $item) use ($origin, $key) {
$cachedTime = new DateInterval(self::CACHE_PUBLIC_URL_TIME);
$item->expiresAfter($cachedTime);
$item->tag(['public-url-files']);
$expires = new DateTime();
$expires->add($cachedTime);
try {
return $this->cloudFrontClient->getSignedUrl([
'url' => $this->distributionUrl.$origin."/".$key,
'expires' => $expires->getTimestamp(),
'private_key' => $this->keyPath,
'key_pair_id' => $this->keyId
]);
} catch (AwsException $e) {
return 'Error: ' . $e->getAwsErrorMessage();
}
}
);
}
}
when I call getSignedUrl()
when executing the return $this->cloudFrontClient->getSignedUrl
I pass the parameters:
url => https://d1andaiyzi47n1.cloudfront.net/user-files/e230a742-e5d5-4c51-ac76-32dc1ceadb91/post/838e9873-0c65-4525-a563-87a0cfb8b283/64f4e586a3cae4.40728513.png
policy => {
"Statement": [
{
"Resource": "https://d1andaiyzi46n1.cloudfront.net/user-files/e230a742-e5d5-4c51-ac76-32dc1ceadb91/post/838e9873-0c65-4525-a563-87a0cfb8b283/64f4e586a3cae4.40728513.png",
"IpAddress": {"AWS:SourceIp": "***."***.."***.."***./32"},
"Condition": {
"DateLessThan": {"AWS:EpochTime": 1693799943}
}
}
]
}
at the end it does return a url that appears to be signed correctly e.g.
https://d1andaiyzi46n1.cloudfront.net/user-files/e230a742-e5d5-4c51-ac76-32dc1ceadb91/post/838e9873-0c65-4525-a563-87a0cfb8b283/64f4e586a3cae4.40728513.png?Policy=eyJT...mh-79n8SA__&Key-Pair-Id=K2Z8L65Y2UGBCT
I see this url in the time limit that I set as well as from the ip that I set. however when I open that url signed in the browser I get the following message
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>5EAYW06QE5218T9T</RequestId>
<HostId>smMq546FKNMqAUNepRJu/bU1FNY3oL9OQtR28JE0UcqLQir5uYVjyMDHsstnpLrU0tHsTPU/N48=</HostId>
</Error>
I am also doing the manual test from the aws cli and the same thing happens, it says access denied.
here is the aws command
aws cloudfront sign --url 'https://d1andaiyzi46n1.cloudfront.net/user-files/e230a742-e5d5-4c51-ac76-32dc1ceadb91/post/838e9873-0c65-4525-a563-87a0cfb8b283/64f4e586a3cae4.40728513.png' --key-pair-id 'K2Z8L65Y2UCBGT' --private-key file://C:/Users/.../secrets/cloudfront-post-files-key1_private.pem --date-less-than '2024-09-04T12:00:00Z'
The logs that cloudfront generates do not give much information only that it seems to be some configuration or permissions issue, but it does not give any indication of what it could be. This is supported by the fact that as soon as the request arrives to cloudfront immediately launches the log with the error, without having a reasonable waiting time to give rise to another diagnosis.
It should be noted that I already had a configuration with only one origin working correctly, however when I tried to add another origin from a different s3 bucket is when neither of the two origins worked.
note: all ids, links and service names are not the real ones. they were modified for the purpose of making the details of the problem as clear as possible.
Thanks for your comment. I am trying to configure 'Private Content' in CloudFront, where the goal is to make the S3 bucket only accessible via signed CloudFront URLs and block direct access to S3. The current configuration ensures that public access cannot be granted through bucket policies or public access points. Allowing ACLs (as you suggest) could open up possibilities for direct public access, which is not desired in this scenario.
I'm trying to pay attention to the fact that your logic is reversed. When it's "Not selected" - meaning this type of access is NOT blocked. Your policy you assigned to grant access only for CF just not working because you blocked using policies but intention was to block ACL (which are not blocked)