aws-cloudfront-oac-private-reach
A CloudFront distribution configured with OAC or OAI on an S3 origin can reach a private (non-public) S3 bucket on behalf of viewers. The distribution acts as a controlled gateway to otherwise-private data.
match (record)
{
"field": "DistributionConfig.Origins.Items",
"resource_type": "AWS::CloudFront::Distribution"
}
where
origin.OriginAccessControlId is non-empty (OAC) OR origin.S3OriginConfig.OriginAccessIdentity is non-empty (OAI)
origin.DomainName matches *.s3.<region>.amazonaws.com or *.s3.amazonaws.com (REST endpoint, not website endpoint)
S3 bucket policy grants s3:GetObject to cloudfront.amazonaws.com (OAC) or the OAI CanonicalUser (OAI)
emit
| source type | Network |
|---|---|
| target type | Data |
| source | <CloudFront distribution node (acting as the reachability actor via OAC/OAI)> |
| target | <S3 bucket node backing the origin> |
| conditions | resource_policy service_state |
| state logic | ACTIVE when OriginAccessControlId or OriginAccessIdentity is set on the origin AND the S3 bucket policy grants the corresponding principal (cloudfront.amazonaws.com with aws:SourceArn == distribution ARN for OAC, or the OAI CanonicalUser for OAI) s3:GetObject. CONDITIONAL(resource_policy) when the OAC/OAI field is set but the S3 bucket policy has not been updated to grant access (CloudFront will receive 403 from S3 in this state). BLOCKED when the S3 bucket explicitly denies cloudfront.amazonaws.com. |
Narrative
CloudFront distribution {source.name} is configured with {oac_or_oai} on its S3 origin {target.name}. CloudFront signs requests as cloudfront.amazonaws.com (OAC) or the OAI canonical user and the S3 bucket policy grants access - the distribution can fetch objects from this otherwise-private bucket on behalf of any viewer. If an attacker can modify the distribution's origin config or the bucket policy, private S3 data may be served publicly via the CDN.
Raw rule rules/derived/aws/cloudfront.yaml
id: aws-cloudfront-oac-private-reach
emits: PrivateReachability
description: A CloudFront distribution configured with OAC or OAI on an S3 origin can reach a private
(non-public) S3 bucket on behalf of viewers. The distribution acts as a controlled gateway to otherwise-private
data.
applies_to:
- aws
match_record:
resource_type: AWS::CloudFront::Distribution
field: DistributionConfig.Origins.Items
where:
- origin.OriginAccessControlId is non-empty (OAC) OR origin.S3OriginConfig.OriginAccessIdentity is non-empty
(OAI)
- origin.DomainName matches *.s3.<region>.amazonaws.com or *.s3.amazonaws.com (REST endpoint, not website
endpoint)
- S3 bucket policy grants s3:GetObject to cloudfront.amazonaws.com (OAC) or the OAI CanonicalUser (OAI)
emit:
source_type: Network
target_type: Data
source: <CloudFront distribution node (acting as the reachability actor via OAC/OAI)>
target: <S3 bucket node backing the origin>
state: ACTIVE
permissions: []
api_source: cloudfront:GetDistributionConfig + s3:GetBucketPolicy
evidence_field: DistributionConfig.Origins.Items[*].OriginAccessControlId | S3OriginConfig.OriginAccessIdentity
conditions:
- resource_policy
- service_state
state_logic: ACTIVE when OriginAccessControlId or OriginAccessIdentity is set on the origin AND the
S3 bucket policy grants the corresponding principal (cloudfront.amazonaws.com with aws:SourceArn ==
distribution ARN for OAC, or the OAI CanonicalUser for OAI) s3:GetObject. CONDITIONAL(resource_policy)
when the OAC/OAI field is set but the S3 bucket policy has not been updated to grant access (CloudFront
will receive 403 from S3 in this state). BLOCKED when the S3 bucket explicitly denies cloudfront.amazonaws.com.
derived_from:
- DistributionConfig.Origins.Items[*].OriginAccessControlId | S3OriginConfig.OriginAccessIdentity observation
false_positive_note: "OAC SCOPE: If the S3 bucket policy grants s3:GetObject to cloudfront.amazonaws.com\
\ WITHOUT the aws:SourceArn condition scoped to this distribution's ARN, ANY CloudFront distribution\
\ (including those in other AWS accounts) can access the bucket by configuring this S3 domain as an\
\ origin. Flag this as a higher-severity misconfiguration (confidence: 0.95 for the PrivateReachability\
\ edge plus a ContainsSecret note if the bucket contains sensitive data). OAI is deprecated: OAI uses\
\ a CanonicalUser ID in the bucket policy; the principal is effectively CloudFront-managed. Still\
\ emit PrivateReachability \u2014 OAI provides the same private-reach capability as OAC. Website endpoints\
\ (*.s3-website-*.amazonaws.com) do NOT support OAC/OAI; do NOT emit PrivateReachability for those\
\ origins. The S3 data-plane CanReadData derivation (if private S3 data becomes public via CDN misconfiguration)\
\ is the responsibility of s3.yaml + network-chains linchpin, not this rule. Emit the FACT here; let\
\ the linchpin compose the consequence."
schema_note: 'PrivateReachability.source is declared as [Compute, Messaging] in edges.yaml; CloudFront
distributions map to PublicEndpoint (Network class). We emit source_type: Network (PublicEndpoint
is a Network subtype). This is a known schema deviation: the declared source set should be extended
to include PublicEndpoint, or the distribution''s OAC/OAI MachineIdentity should be the source (losing
the distribution->s3 edge semantics in favor of oac->s3). Pending schema extension, we use Network
(consistent with CanNetworkReach.source) as the most accurate available class. Semantically this edge
means "the distribution''s OAC/OAI signing mechanism can reach the private S3 endpoint."'
narrative: "CloudFront distribution {source.name} is configured with {oac_or_oai} on its S3 origin {target.name}.\
\ CloudFront signs requests as cloudfront.amazonaws.com (OAC) or the OAI canonical user and the S3\
\ bucket policy grants access \u2014 the distribution can fetch objects from this otherwise-private\
\ bucket on behalf of any viewer. If an attacker can modify the distribution's origin config or the\
\ bucket policy, private S3 data may be served publicly via the CDN."