Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,163 @@ Resources:
Distributions: []cloudfront.Distribution{{}},
},
},

{
name: "v2 logging configured",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: "redirect-to-https"

CloudFrontAccessLogsDeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
LogType: ACCESS_LOGS
Name: cloudfront-log-delivery-source
ResourceArn: !Sub
- arn:aws:cloudfront::${AWS::AccountId}:distribution/${D}
- D: !GetAtt CloudFrontDistribution.Id

CloudFrontAccessLogsDelivery:
Type: AWS::Logs::Delivery
Properties:
DeliverySourceName: cloudfront-log-delivery-source
`,
expected: cloudfront.Cloudfront{
Distributions: []cloudfront.Distribution{
{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: types.BoolTest(true),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{
ViewerProtocolPolicy: types.StringTest("redirect-to-https"),
},
},
},
},
},
{
name: "v2 logging source exists but no delivery",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: "redirect-to-https"

CloudFrontAccessLogsDeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
LogType: ACCESS_LOGS
Name: cloudfront-log-delivery-source
ResourceArn: !Sub
- arn:aws:cloudfront::${AWS::AccountId}:distribution/${D}
- D: !GetAtt CloudFrontDistribution.Id
`,
expected: cloudfront.Cloudfront{
Distributions: []cloudfront.Distribution{
{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: types.BoolTest(false),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{
ViewerProtocolPolicy: types.StringTest("redirect-to-https"),
},
},
},
},
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two cases missing here:

  1. LogType other than ACCESS_LOGS → expect false (covers the LogType filter).
  2. DeliverySourceName given as !Ref CloudFrontAccessLogsDeliverySource (logical ID) instead of the literal name → expect true.


{
name: "v2 logging with non-access log_type yields false",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: "redirect-to-https"

CloudFrontAccessLogsDeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
LogType: ERROR_LOGS
Name: cloudfront-log-delivery-source
ResourceArn: !Sub
- arn:aws:cloudfront::${AWS::AccountId}:distribution/${D}
- D: !GetAtt CloudFrontDistribution.Id

CloudFrontAccessLogsDelivery:
Type: AWS::Logs::Delivery
Properties:
DeliverySourceName: cloudfront-log-delivery-source
`,
expected: cloudfront.Cloudfront{
Distributions: []cloudfront.Distribution{
{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: types.BoolTest(false),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{
ViewerProtocolPolicy: types.StringTest("redirect-to-https"),
},
},
},
},
},
{
name: "v2 logging delivery source ref via logical ID yields true",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
ViewerProtocolPolicy: "redirect-to-https"

CloudFrontAccessLogsDeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
LogType: ACCESS_LOGS
Name: cloudfront-log-delivery-source
ResourceArn: !Sub
- arn:aws:cloudfront::${AWS::AccountId}:distribution/${D}
- D: !GetAtt CloudFrontDistribution.Id

CloudFrontAccessLogsDelivery:
Type: AWS::Logs::Delivery
Properties:
DeliverySourceName: !Ref CloudFrontAccessLogsDeliverySource
`,
expected: cloudfront.Cloudfront{
Distributions: []cloudfront.Distribution{
{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: types.BoolTest(true),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{
ViewerProtocolPolicy: types.StringTest("redirect-to-https"),
},
},
},
},
},
}

for _, tt := range tests {
Expand Down
32 changes: 32 additions & 0 deletions pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package cloudfront

import (
"strings"

"github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront"
"github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser"
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
)

func getDistributions(ctx parser.FileContext) (distributions []cloudfront.Distribution) {

distributionResources := ctx.GetResourcesByType("AWS::CloudFront::Distribution")
deliverySources := ctx.GetResourcesByType("AWS::Logs::DeliverySource")
deliveries := ctx.GetResourcesByType("AWS::Logs::Delivery")

for _, r := range distributionResources {
distribution := cloudfront.Distribution{
Expand All @@ -16,6 +21,10 @@ func getDistributions(ctx parser.FileContext) (distributions []cloudfront.Distri
Logging: cloudfront.Logging{
Metadata: r.Metadata(),
Bucket: r.GetStringProperty("DistributionConfig.Logging.Bucket"),
V2: cloudfront.LoggingV2{
Metadata: r.Metadata(),
Enabled: hasV2Logging(r, deliverySources, deliveries),
},
},
DefaultCacheBehaviour: getDefaultCacheBehaviour(r),
OrdererCacheBehaviours: nil,
Expand Down Expand Up @@ -43,3 +52,26 @@ func getDefaultCacheBehaviour(r *parser.Resource) cloudfront.CacheBehaviour {
ViewerProtocolPolicy: defaultCache.GetStringProperty("ViewerProtocolPolicy"),
}
}

func hasV2Logging(distribution *parser.Resource, deliverySources, deliveries []*parser.Resource) iacTypes.BoolValue {

for _, source := range deliverySources {
if !source.GetStringProperty("LogType").EqualTo("ACCESS_LOGS") {
continue
}
resourceArn := source.GetStringProperty("ResourceArn")
if !strings.Contains(resourceArn.Value(), distribution.ID()) {
continue
}
sourceName := source.GetStringProperty("Name")

for _, delivery := range deliveries {
deliverySourceName := delivery.GetStringProperty("DeliverySourceName")
if deliverySourceName.Value() == sourceName.Value() || deliverySourceName.Value() == source.ID() {
return iacTypes.Bool(true, delivery.Metadata())
}
}

}
return iacTypes.Bool(false, distribution.Metadata())
}
28 changes: 27 additions & 1 deletion pkg/iac/adapters/terraform/aws/cloudfront/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ func adaptDistributions(modules terraform.Modules) []cloudfront.Distribution {
var distributions []cloudfront.Distribution
for _, module := range modules {
for _, resource := range module.GetResourcesByType("aws_cloudfront_distribution") {
distributions = append(distributions, adaptDistribution(resource))
distribution := adaptDistribution(resource)

distribution.Logging.V2 = cloudfront.LoggingV2{
Metadata: resource.GetMetadata(),
Enabled: hasV2Logging(modules, resource),
}

distributions = append(distributions, distribution)
}
}
return distributions
Expand Down Expand Up @@ -77,3 +84,22 @@ func adaptDistribution(resource *terraform.Block) cloudfront.Distribution {

return distribution
}

func hasV2Logging(modules terraform.Modules, distributionBlock *terraform.Block) types.BoolValue {
metadata := distributionBlock.GetMetadata()

sources := modules.GetReferencingResources(distributionBlock, "aws_cloudwatch_log_delivery_source", "resource_arn")
for _, source := range sources {
if !source.GetAttribute("log_type").Equals("ACCESS_LOGS") {
continue
}

deliveries := modules.GetReferencingResources(source, "aws_cloudwatch_log_delivery", "delivery_source_name")
if len(deliveries) > 0 {
return types.Bool(true, metadata)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as in CF, it's best to return the aws_cloudwatch_log_delivery metadata.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until now it was under assumption that merely an existence of a aws_cloudwatch_log_delivery would confirm the V2 to be true, here it didn't reference the specific resource explicitly, so in case at least one resource exists(usual case) should i do return types.Bool(true, deliveries[0].GetMetadata()). Is this the correct approach?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's fine, since there's supposed to be at least one delivery resource here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also just a note for both CF and Terraform in case of false, distribution metadata is returned

}
}

return types.Bool(false, metadata)

}
93 changes: 93 additions & 0 deletions pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,99 @@ func Test_adaptDistribution(t *testing.T) {
}
}

func Test_adaptDistributionV2(t *testing.T) {

@nikpivkin nikpivkin Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a negative case where the delivery source references the distribution and a delivery exists, but log_type is not ACCESS_LOGS (e.g. ERROR_LOGS) → expect false? Right now the log_type filter branch isn't covered, so a regression there would go unnoticed.

tests := []struct {
name string
terraform string
expected cloudfront.Distribution
}{
{
name: "v2 logging configured",
terraform: `
resource "aws_cloudfront_distribution" "example" {}

resource "aws_cloudwatch_log_delivery_source" "example" {
log_type = "ACCESS_LOGS"
resource_arn = aws_cloudfront_distribution.example.arn
}

resource "aws_cloudwatch_log_delivery" "example" {
delivery_source_name = aws_cloudwatch_log_delivery_source.example.name
}
`,
expected: cloudfront.Distribution{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: iacTypes.BoolTest(true),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{},
ViewerCertificate: cloudfront.ViewerCertificate{
MinimumProtocolVersion: iacTypes.StringTest("TLSv1"),
},
},
},
{
name: "v2 logging source exists but no delivery",
terraform: `
resource "aws_cloudfront_distribution" "example" {}

resource "aws_cloudwatch_log_delivery_source" "example" {
log_type = "ACCESS_LOGS"
resource_arn = aws_cloudfront_distribution.example.arn
}
`,
expected: cloudfront.Distribution{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: iacTypes.BoolTest(false),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{},
ViewerCertificate: cloudfront.ViewerCertificate{
MinimumProtocolVersion: iacTypes.StringTest("TLSv1"),
},
},
},

{
name: "v2 logging with non-access log_type",
terraform: `
resource "aws_cloudfront_distribution" "example" {}

resource "aws_cloudwatch_log_delivery_source" "example" {
log_type = "ERROR_LOGS"
resource_arn = aws_cloudfront_distribution.example.arn
}

resource "aws_cloudwatch_log_delivery" "example" {
delivery_source_name = aws_cloudwatch_log_delivery_source.example.name
}
`,
expected: cloudfront.Distribution{
Logging: cloudfront.Logging{
V2: cloudfront.LoggingV2{
Enabled: iacTypes.BoolTest(false),
},
},
DefaultCacheBehaviour: cloudfront.CacheBehaviour{},
ViewerCertificate: cloudfront.ViewerCertificate{
MinimumProtocolVersion: iacTypes.StringTest("TLSv1"),
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf")
adapted := Adapt(modules)
require.Len(t, adapted.Distributions, 1)
testutil.AssertDefsecEqual(t, test.expected, adapted.Distributions[0])
})
}
}

func TestLines(t *testing.T) {
src := `
resource "aws_cloudfront_distribution" "example" {
Expand Down
6 changes: 6 additions & 0 deletions pkg/iac/providers/aws/cloudfront/cloudfront.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ type Distribution struct {
ViewerCertificate ViewerCertificate
}

type LoggingV2 struct {
Metadata iacTypes.Metadata
Enabled iacTypes.BoolValue
}

type Logging struct {
Metadata iacTypes.Metadata
Bucket iacTypes.StringValue
V2 LoggingV2
}

type CacheBehaviour struct {
Expand Down
Loading