Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
metadata:
name: "Ensure CloudFront distribution has Access Logging enabled"
id: "CKV_AWS_86"
category: "LOGGING"
scope:
provider: "aws"
definition:
and:
- cond_type: filter
attribute: resource_type
value:
- aws_cloudfront_distribution
operator: within
- or:
- cond_type: attribute
resource_types:
- aws_cloudfront_distribution
attribute: logging_config.bucket
operator: exists
- and:
- cond_type: connection
resource_types:
- aws_cloudfront_distribution
connected_resource_types:
- aws_cloudwatch_log_delivery_source
operator: exists
- cond_type: connection
resource_types:
- aws_cloudwatch_log_delivery_source
connected_resource_types:
- aws_cloudwatch_log_delivery
operator: exists
- cond_type: connection
resource_types:
- aws_cloudwatch_log_delivery
connected_resource_types:
- aws_cloudwatch_log_delivery_destination
operator: exists
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ def get_expected_value(self):
return ANY_VALUE


check = CloudfrontDistributionLogging()
# CKV_AWS_86 is implemented as a graph check to support both legacy and v2 logging models.
# This legacy resource check remains as reference-only and is intentionally not registered.
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
resource "aws_cloudfront_distribution" "pass_v1" {
comment = "legacy logging"

logging_config {
bucket = "logs.s3.amazonaws.com"
}

origin {
domain_name = "example.com"
origin_id = "example-origin"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

enabled = true
default_root_object = "index.html"

default_cache_behavior {
target_origin_id = "example-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]

forwarded_values {
query_string = false

cookies {
forward = "none"
}
}
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

resource "aws_cloudfront_distribution" "pass_v2" {
comment = "v2 logging via cloudwatch log delivery"

origin {
domain_name = "example.org"
origin_id = "example-origin-2"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

enabled = true
default_root_object = "index.html"

default_cache_behavior {
target_origin_id = "example-origin-2"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]

forwarded_values {
query_string = false

cookies {
forward = "none"
}
}
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

resource "aws_cloudwatch_log_delivery_source" "pass_v2" {
name = "cf-source-pass-v2"
log_type = "ACCESS_LOGS"
resource_arn = aws_cloudfront_distribution.pass_v2.arn
}

resource "aws_cloudwatch_log_delivery_destination" "pass_v2" {
name = "cf-dest-pass-v2"

delivery_destination_configuration {
destination_resource_arn = "arn:aws:logs:us-east-1:111111111111:log-group:/aws/cloudfront/pass-v2:*"
}
}

resource "aws_cloudwatch_log_delivery" "pass_v2" {
delivery_source_name = aws_cloudwatch_log_delivery_source.pass_v2.name
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.pass_v2.arn
}

resource "aws_cloudfront_distribution" "fail_no_logging" {
comment = "no logging at all"

origin {
domain_name = "example.net"
origin_id = "example-origin-3"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

enabled = true
default_root_object = "index.html"

default_cache_behavior {
target_origin_id = "example-origin-3"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]

forwarded_values {
query_string = false

cookies {
forward = "none"
}
}
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

resource "aws_cloudfront_distribution" "fail_v2_incomplete_chain" {
comment = "source exists, delivery missing"

origin {
domain_name = "example.edu"
origin_id = "example-origin-4"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

enabled = true
default_root_object = "index.html"

default_cache_behavior {
target_origin_id = "example-origin-4"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]

forwarded_values {
query_string = false

cookies {
forward = "none"
}
}
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

resource "aws_cloudwatch_log_delivery_source" "fail_v2_incomplete_chain" {
name = "cf-source-fail-v2"
log_type = "ACCESS_LOGS"
resource_arn = aws_cloudfront_distribution.fail_v2_incomplete_chain.arn
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import os
import unittest
from pathlib import Path

import pytest

from checkov.common.models.enums import CheckResult
from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.resource.aws.CloudfrontDistributionLogging import check
from checkov.terraform.runner import Runner


class TestCloudfrontDistributionLogging(unittest.TestCase):

def test_failure(self):
resource_conf = {
"comment": "Example",
def test_file_v1_and_v2_logging(self):
test_files_dir = Path(__file__).parent / "example_CloudfrontDistributionLoggingV2"

report = Runner().run(
root_folder=str(test_files_dir),
runner_filter=RunnerFilter(checks=["CKV_AWS_86"]),
)
summary = report.get_summary()

passing_resources = {
"aws_cloudfront_distribution.pass_v1",
"aws_cloudfront_distribution.pass_v2",
"aws_cloudfront_distribution.fail_v2_incomplete_chain",
}
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.FAILED, scan_result)

def test_success(self):
resource_conf = {
"comment": "Example",
"logging_config": [
{
"bucket": "some-arn"
}
],
failing_resources = {
"aws_cloudfront_distribution.fail_no_logging",
}
scan_result = check.scan_resource_conf(conf=resource_conf)
self.assertEqual(CheckResult.PASSED, scan_result)

passed_check_resources = {c.resource for c in report.passed_checks}
failed_check_resources = {c.resource for c in report.failed_checks}

self.assertEqual(summary["passed"], len(passing_resources))
self.assertEqual(summary["failed"], len(failing_resources))
self.assertEqual(summary["skipped"], 0)
self.assertEqual(summary["parsing_errors"], 0)

self.assertEqual(passing_resources, passed_check_resources)
self.assertEqual(failing_resources, failed_check_resources)

@pytest.mark.skip("Need to handle null variables")
def test_null_var_651(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pass:
- "aws_cloudfront_distribution.pass_v1"
- "aws_cloudfront_distribution.pass"
- "aws_cloudfront_distribution.fail_v2_incomplete_chain"
fail:
- "aws_cloudfront_distribution.fail"
Loading