Reputation: 392
Using multiple CloudFronts with one bucket doesn't work very well because the only way I can give access in CloudFormation is to add a BucketPolicy
to the template. With multiple apps/templates this will always overwrite the entire bucket policy to add permission for the S3 Origin Access Identity
.
S3OriginIdentity:
Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: S3 Origin Identity
S3OriginIdentityS3ReadPolicy:
Type: "AWS::S3::BucketPolicy"
Properties:
Bucket: my-bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: my-cloudfront-read-access
Action:
- s3:GetObject
Effect: Allow
Principal:
CanonicalUser:
Fn::GetAtt: S3OriginIdentity.S3CanonicalUserId
Resource: arn:aws:s3:::my-bucket/path/*
To solve this issue it occurred to me that I could use a condition in the bucket policy to give read access to any Cloudfront identity. From looking at the documentation it appears that aws:SourceArn
or aws:PrincipalArn
should do the trick. Here is an example policy I tried.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": 1,
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity*"
}
},
"Principal": "*"
}]
}
When I used aws:PrincipalArn
, the console wouldn't even let me save it (a generic Access Denied
), but I tried it with the aws:SourceArn
version above, and it did not give access. What am I doing wrong? Or is there a better approach?
Upvotes: 1
Views: 3063
Reputation: 656
Here is a CloudFormation template for the Bucket Policy and other salient parts regarding the CloudFront Origin Access Identity:
AppDistBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref AppDistBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${AppOriginAccessIdentity}
Action:
- s3:GetObject
Resource: !Sub 'arn:aws:s3:::${AppDistBucket}/*'
AppOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub 'CloudFront OAI for ${EnvironmentName}-${DistIdentifier}'
AppDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# ...
Origins:
- DomainName: !Sub ${AppDistBucket}.s3.amazonaws.com
Id: !Sub ${EnvironmentName}-${DistIdentifier}
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${AppOriginAccessIdentity}'
# ...
Also, keep in mind that patience is key. Once properly configured, your distribution may show up with "Access Denied" errors for up to a few hours.
Upvotes: 2
Reputation: 180
I know this is an old question but I have run into a similar issue.
I might be able to shed some light at least on the AccessDenied error when using aws:PrincipalArn. In my case it was because my S3 bucket had block public policy enabled and that aws:PrincipalArn condition isn't enough to qualify as a non-public principal. The required conditions for that can be found here https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html#access-control-block-public-access-policy-status.
What I wasn't able to get around yet was that in Terraform there appears to be no way to query for a previously created OAI. So if I create the OAI, S3 bucket and policy in one module I can't get that OAI for the CloudFront distributions later on.
Upvotes: 0
Reputation: 178966
aws:SourceArn
is listed as "is available for only some services" and refers not to a principal but to resource that is the source of an action, like an SNS topic in an SQS queue policy or an S3 bucket in a Macie policy.
In that light, I don't think it would be applicable, here, and s3:GetObject
doesn't mention it as a possibility -- this action only appears to support a small number of non-global condition keys, all of which are specific to S3: s3:ExistingObjectTag/<key>
, s3:authtype
, s3:signatureage
, s3:signatureversion
, s3:x-amz-content-sha256
.
aws:PrincipalArn
is a global condition key, so it looks like it might have worked, but I'd suggest that you're fortunate that it did not. If it had, then you would have exposed your bucket to all Origin Access Identities for all CloudFront distributions in all AWS accounts. The "access denied" error is unexpected, but might stem from the fact that you lack a permission on arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity*
, which makes sense, since it's not part of in your account -- OAIs are allocated to your account by CloudFront but they aren't, strictly speaking, part of your AWS account -- they're an external entity.¹ In light of this, it seems unlikely that there's a way to express "all of my OAIs" in a policy statement.
I would suggest that the most straightfoward approach to having a single bucket accessible to multiple CloudFront distributions would be to create a single OAI and configure your multiple CloudFront distributions all to use it. Individual OAIs and individual bucket policy grants are not only unwieldy, they also won't scale -- bucket policies are limited to ~20KB in size.
But if that's the direction you want to go, it appears that a Lambda-Backed Custom Resource could be used. The idea here would be to create a Lambda function that will edit the bucket policy and append the new OAI to the Principal
array containing the already-added OAIs. (Principal
in the bucket policy can be either a scalar or an array). The danger of this is that it's a delicate operation -- there's no concurrency guarding of a bucket policy, so if two of these stacks try to modify the policy at effectively the same time (get, edit, put) then one of them will see its updates ignored because they're be overwritten by the other. (Concurrent operations that ultimately in this order -- Read #1, Modify #1, Read #2, Modify #2, Write #1, Write #2 -- result in Write #1's change being lost, because Write #2 stored data that was based on Read #2, which was identical to Read #1.)
¹Justification for this assertion: an OAI can't access objects in a bucket that are created by a different AWS account with write access to your bucket, even if x-amz-acl: bucket-owner-full-control
is applied to the object: "If another AWS account uploads files to your bucket, that account is the owner of those files. Bucket policies only apply to files that the bucket owner owns. This means that if another account uploads files to your bucket, the bucket policy that you created for your OAI will not be evaluated for those files." Exactly the same thing is true if your bucket policy explicitly grants read-access to an IAM user in a different account -- the bucket policy can't delegate read permissions to a user outside the same account when the object was also created by a user from outside the account. Users inside your account can access such objects if the bucket policy allows them to.
Upvotes: 3