diff --git a/.github/workflows/integration_tests_deployment.yml b/.github/workflows/integration_tests_deployment.yml index d1631d4..32ac24f 100644 --- a/.github/workflows/integration_tests_deployment.yml +++ b/.github/workflows/integration_tests_deployment.yml @@ -23,6 +23,9 @@ env: ROUTE53_ACCOUNT: ${{ secrets.ROUTE53_ACCOUNT }} TEST_ROLE_NAME: ${{ secrets.TEST_ROLE_NAME }} ZONE_NAME: ${{ secrets.ZONE_NAME }} + CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }} + CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} + CLOUDFLARE_ZONE_NAME: ${{ secrets.CLOUDFLARE_ZONE_NAME }} jobs: integration_tests_deployment: @@ -47,7 +50,7 @@ jobs: - name: Install dependencies run: | - pip install -r requirements-dev.txt + pip install -r requirements-tests.txt - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4 diff --git a/integration_tests/deployment/cloudflare/test_cf_vulnerabilities.py b/integration_tests/deployment/cloudflare/test_cf_vulnerabilities.py new file mode 100644 index 0000000..b5a6f02 --- /dev/null +++ b/integration_tests/deployment/cloudflare/test_cf_vulnerabilities.py @@ -0,0 +1,111 @@ +# Cloudflare Python version 3.1.1 +import os +from time import sleep + +from assertpy import assert_that + +from integration_tests.deployment.aws.utils.awslambda import invoke_lambda +from integration_tests.deployment.aws.utils.dynamodb import vulnerability_detected_in_time_period +from integration_tests.deployment.aws.utils.general import random_string +from integration_tests.deployment.cloudflare.utils.cloudflare_dns import create_dns_record +from integration_tests.deployment.cloudflare.utils.cloudflare_dns import delete_dns_record +from integration_tests.deployment.cloudflare.utils.cloudflare_dns import list_dns_records +from integration_tests.deployment.cloudflare.utils.cloudflare_dns import list_dns_zones +from utils.utils_db import db_get_unfixed_vulnerability_found_date_time + + +def get_cf_zone_id(zone_name): + zones = list_dns_zones() + for zone in zones: + if zone.name == zone_name: + return zone.id + return None + + +def get_cf_record_id(zone_id, zone_name, record_fqdn): + records = list_dns_records(zone_id, zone_name, record_fqdn) + if records: + return records[0].id + return None + + +def create_vulnerable_cf_record(zone_id, zone_name, record_name, record_type, record_value): + create_dns_record(zone_id, zone_name, record_name, record_type, record_value) + record_fqdn = f"{record_name}.{zone_name}" + print(f"Created vulnerable {record_type.upper()} record: {record_fqdn} {record_type} {record_value}") + + +def delete_vulnerable_cf_record(zone_id, zone_name, record_name): + record_fqdn = f"{record_name}.{zone_name}" + records = list_dns_records(zone_id, zone_name, record_fqdn) + + for record in records: + delete_dns_record(zone_id, zone_name, record["id"]) + print(f"Deleted vulnerable {record['type'].upper()} record: {record_fqdn} {record['type']} {record['content']}") + + +def cf_vulnerability_reported_fixed_in_time_period(update_lambda_name, domain, seconds): + while seconds > 0: + invoke_lambda(update_lambda_name) + if not db_get_unfixed_vulnerability_found_date_time(domain): + print(f"{domain} vulnerability reported as fixed") + return True + sleep(5) + print(f"{seconds} seconds remaining") + seconds -= 5 + + return False + + +def test_cf_vulnerabilities_detected(): + project = os.environ["PROJECT"] + environment = os.environ["ENVIRONMENT"] + cf_zone_name = os.environ["CLOUDFLARE_ZONE_NAME"] + + cf_zone_id = get_cf_zone_id(cf_zone_name) + cf_scan_lambda_name = f"{project}-cloudflare-scan-{environment}" + update_lambda_name = f"{project}-update-{environment}" + cname_suffix = random_string() + ns_suffix = random_string() + cname_record_name = f"vulnerable-{cname_suffix}" + cname_record_fqdn = f"vulnerable-{cname_suffix}.{cf_zone_name}" + cname_value = f"{cname_suffix}.trafficmanager.net" + ns_record_name = f"vulnerable-{ns_suffix}" + ns_record_fqdn = f"vulnerable-{ns_suffix}.{cf_zone_name}" + ns_record_value = "ns1.amazon.com" + + # Create vulnerable records in Cloudflare DNS + create_vulnerable_cf_record(cf_zone_id, cf_zone_name, cname_record_name, "CNAME", cname_value) + create_vulnerable_cf_record(cf_zone_id, cf_zone_name, ns_record_name, "NS", ns_record_value) + + # Invoke Accounts Lambda function + invoke_lambda(cf_scan_lambda_name) + + # test if vulnerabilities detected within specified time period + cname_vulnerability_found = vulnerability_detected_in_time_period(f"{cname_record_fqdn}", 120) + ns_vulnerability_found = vulnerability_detected_in_time_period(f"{ns_record_fqdn}", 120) + + # Get IDs of vulnerable records + cname_record_id = get_cf_record_id(cf_zone_id, cf_zone_name, cname_record_fqdn) + ns_record_id = get_cf_record_id(cf_zone_id, cf_zone_name, ns_record_fqdn) + + # Delete vulnerable records + delete_vulnerable_cf_record(cf_zone_id, cf_zone_name, cname_record_id) + delete_vulnerable_cf_record(cf_zone_id, cf_zone_name, ns_record_id) + + # test if vulnerabilities reported as fixed within specified time period + cname_vulnerability_reported_fixed = cf_vulnerability_reported_fixed_in_time_period( + update_lambda_name, + f"{cname_record_name}.", + 300, + ) + ns_vulnerability_reported_fixed = cf_vulnerability_reported_fixed_in_time_period( + update_lambda_name, + f"{ns_record_name}.", + 300, + ) + + assert_that(cname_vulnerability_found).is_true() + assert_that(ns_vulnerability_found).is_true() + assert_that(cname_vulnerability_reported_fixed).is_true() + assert_that(ns_vulnerability_reported_fixed).is_true() diff --git a/integration_tests/deployment/cloudflare/utils/cloudflare_dns.py b/integration_tests/deployment/cloudflare/utils/cloudflare_dns.py new file mode 100644 index 0000000..1ddaee8 --- /dev/null +++ b/integration_tests/deployment/cloudflare/utils/cloudflare_dns.py @@ -0,0 +1,55 @@ +# Cloudflare Python version 3.1.1 - DNS Utils +from cloudflare import Cloudflare +from cloudflare import CloudflareError + + +def create_dns_record(zone_id, zone_name, record_name, record_type, record_value, ttl=60): + cf = Cloudflare() + print(f"Creating DNS record {record_name} in Cloudflare DNS zone {zone_name}") + + try: + cf.dns.records.create(zone_id=zone_id, name=record_name, type=record_type, content=record_value, ttl=ttl) + print(f"Successfully created DNS record {record_name} in Cloudflare DNS zone {zone_name}") + except CloudflareError as e: + print(f"Failed to create DNS record {record_name} in Cloudflare DNS zone {zone_name}") + print(f"Error: {e}") + + +def delete_dns_record(zone_id, record_id, record_fqdn): + cf = Cloudflare() + print(f"Deleting Cloudflare DNS record {record_fqdn}") + + try: + cf.dns.records.delete(zone_id=zone_id, dns_record_id=record_id) + print(f"Successfully deleted Cloudflare DNS record {record_fqdn}") + except CloudflareError as e: + print(f"Failed to delete Cloudflare DNS record {record_fqdn}") + print(f"Error: {e}") + + +def list_dns_records(zone_id, zone_name, record_fqdn=None): + cf = Cloudflare() + print(f"Listing DNS records in Cloudflare DNS zone {zone_name}") + + try: + records = cf.dns.records.list(zone_id=zone_id, name=record_fqdn).result + print(f"Successfully listed DNS records in Cloudflare DNS zone {zone_name}") + return records + except CloudflareError as e: + print(f"Failed to list DNS records in Cloudflare DNS zone {zone_name}") + print(f"Error: {e}") + return None + + +def list_dns_zones(): + cf = Cloudflare() + print("Listing DNS zones in Cloudflare") + + try: + zones = cf.zones.list().result + print("Successfully listed DNS zones in Cloudflare") + return zones + except CloudflareError as e: + print("Failed to list DNS zones in Cloudflare") + print(f"Error: {e}") + return None diff --git a/requirements-tests.txt b/requirements-tests.txt new file mode 100644 index 0000000..3de803a --- /dev/null +++ b/requirements-tests.txt @@ -0,0 +1,14 @@ +boto3==1.34.25 +cloudflare==3.1.1 +dnspython==2.7.0 +requests==2.32.3 +regex==2024.11.6 +black==24.10.0 +prospector==1.13.0 +pytest==8.3.3 +pytest-cov==6.0.0 +assertpy==1.1 +moto[sts, iam]==4.2.14 +requests-mock==1.12.1 +checkov==3.2.286 +pre-commit==4.0.1