diff --git a/prowler/changelog.d/cloudflare-challenge-ttl-none.fixed.md b/prowler/changelog.d/cloudflare-challenge-ttl-none.fixed.md new file mode 100644 index 0000000000..26c22b4b3b --- /dev/null +++ b/prowler/changelog.d/cloudflare-challenge-ttl-none.fixed.md @@ -0,0 +1 @@ +`zone_challenge_passage_configured` no longer fails when a Cloudflare zone has no challenge passage TTL configured diff --git a/prowler/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured.py b/prowler/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured.py index 59cabdadb5..7fbf203fcb 100644 --- a/prowler/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured.py +++ b/prowler/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured.py @@ -29,17 +29,23 @@ class zone_challenge_passage_configured(Check): metadata=self.metadata(), resource=zone, ) - # API returns seconds, convert to minutes - challenge_ttl_minutes = zone.settings.challenge_ttl // 60 - - if min_minutes <= challenge_ttl_minutes <= max_minutes: - report.status = "PASS" - report.status_extended = f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name}." - else: + challenge_ttl = zone.settings.challenge_ttl + if challenge_ttl is None: report.status = "FAIL" report.status_extended = ( - f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name} " - f"(recommended: between {min_minutes} and {max_minutes} minutes)." + f"Challenge Passage is not configured for zone {zone.name}." ) + else: + # API returns seconds, convert to minutes + challenge_ttl_minutes = challenge_ttl // 60 + if min_minutes <= challenge_ttl_minutes <= max_minutes: + report.status = "PASS" + report.status_extended = f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name}." + else: + report.status = "FAIL" + report.status_extended = ( + f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name} " + f"(recommended: between {min_minutes} and {max_minutes} minutes)." + ) findings.append(report) return findings diff --git a/tests/providers/cloudflare/cloudflare_provider_test.py b/tests/providers/cloudflare/cloudflare_provider_test.py index c3ba6d75e2..5db4c79537 100644 --- a/tests/providers/cloudflare/cloudflare_provider_test.py +++ b/tests/providers/cloudflare/cloudflare_provider_test.py @@ -9,7 +9,6 @@ from prowler.providers.cloudflare.exceptions.exceptions import ( CloudflareInvalidAPIKeyError, CloudflareInvalidAPITokenError, CloudflareNoAccountsError, - CloudflareUserTokenRequiredError, ) from prowler.providers.cloudflare.models import ( CloudflareAccount, @@ -308,8 +307,8 @@ class TestCloudflareValidateCredentials: CloudflareProvider.validate_credentials(session) mock_client.user.get.assert_called_once() - def test_validate_credentials_user_token_required(self): - """Test that user token required error is raised for Account tokens.""" + def test_validate_credentials_account_token_without_accounts(self): + """Test that account tokens fall back to account discovery.""" mock_client = MagicMock() # Simulate error code 9109 - user-level authentication required from cloudflare._exceptions import PermissionDeniedError @@ -327,8 +326,9 @@ class TestCloudflareValidateCredentials: api_email=None, ) - with pytest.raises(CloudflareUserTokenRequiredError): + with pytest.raises(CloudflareNoAccountsError): CloudflareProvider.validate_credentials(session) + mock_client.accounts.list.assert_called_once() def test_validate_credentials_invalid_api_token(self): """Test that invalid API token error is raised.""" @@ -487,8 +487,8 @@ class TestCloudflareTestConnection: assert connection.is_connected is False assert isinstance(connection.error, CloudflareInvalidAPITokenError) - def test_test_connection_user_token_required(self): - """Test that user token required error is properly returned.""" + def test_test_connection_account_token_without_accounts(self): + """Test that account tokens without accessible accounts return an error.""" mock_client = MagicMock() from cloudflare._exceptions import PermissionDeniedError @@ -512,9 +512,9 @@ class TestCloudflareTestConnection: ) assert connection.is_connected is False - assert isinstance(connection.error, CloudflareUserTokenRequiredError) - # Verify the error message is user-friendly - assert "User-level API token required" in str(connection.error) + assert isinstance(connection.error, CloudflareNoAccountsError) + assert "No Cloudflare accounts found" in str(connection.error) + mock_client.accounts.list.assert_called_once() def test_test_connection_invalid_api_key(self): """Test that invalid API key error is properly returned.""" diff --git a/tests/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured_test.py b/tests/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured_test.py index dc794104c9..3621073da6 100644 --- a/tests/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured_test.py +++ b/tests/providers/cloudflare/services/zone/zone_challenge_passage_configured/zone_challenge_passage_configured_test.py @@ -240,3 +240,7 @@ class Test_zone_challenge_passage_configured: result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Challenge Passage is not configured for zone {ZONE_NAME}." + ) diff --git a/tests/providers/cloudflare/services/zone/zone_development_mode_disabled/zone_development_mode_disabled_test.py b/tests/providers/cloudflare/services/zone/zone_development_mode_disabled/zone_development_mode_disabled_test.py index a3dc02807e..b42c8280c4 100644 --- a/tests/providers/cloudflare/services/zone/zone_development_mode_disabled/zone_development_mode_disabled_test.py +++ b/tests/providers/cloudflare/services/zone/zone_development_mode_disabled/zone_development_mode_disabled_test.py @@ -102,8 +102,10 @@ class Test_zone_development_mode_disabled: result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" - assert "Development mode is enabled" in result[0].status_extended - assert "bypasses" in result[0].status_extended + assert ( + result[0].status_extended + == f"Development mode is enabled for zone {ZONE_NAME}." + ) def test_zone_development_mode_none(self): zone_client = mock.MagicMock