mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
feat(cloudflare): --account-id filter support (#9894)
Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
065827cd38
commit
80c94faff9
@@ -87,6 +87,30 @@ You can also use zone IDs instead of domain names:
|
||||
prowler cloudflare -f 023e105f4ecef8ad9ca31a8372d0c353
|
||||
```
|
||||
|
||||
## Filtering Accounts
|
||||
|
||||
By default, Prowler scans all accounts accessible with your credentials. If your API Token or API Key has access to multiple Cloudflare accounts, you can restrict the scan to specific accounts using the `--account-id` argument:
|
||||
|
||||
```bash
|
||||
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59
|
||||
```
|
||||
|
||||
You can specify multiple account IDs:
|
||||
|
||||
```bash
|
||||
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59 9a7806061c88ada191ed06f989cc3dac
|
||||
```
|
||||
|
||||
<Note>
|
||||
If any of the provided account IDs are not found among the accounts accessible with your credentials, Prowler will raise an error and stop execution.
|
||||
</Note>
|
||||
|
||||
You can combine account and zone filtering to narrow the scan scope further:
|
||||
|
||||
```bash
|
||||
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59 -f example.com
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Prowler uses a configuration file to customize provider behavior. The Cloudflare configuration includes:
|
||||
|
||||
@@ -11,6 +11,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `codebuild_project_webhook_filters_use_anchored_patterns` check for AWS provider to detect CodeBreach vulnerability [(#9840)](https://github.com/prowler-cloud/prowler/pull/9840)
|
||||
- `exchange_shared_mailbox_sign_in_disabled` check for M365 provider [(#9828)](https://github.com/prowler-cloud/prowler/pull/9828)
|
||||
- CloudTrail Timeline abstraction for querying resource modification history [(#9101)](https://github.com/prowler-cloud/prowler/pull/9101)
|
||||
- Cloudflare `--account-id` filter argument [(#9894)](https://github.com/prowler-cloud/prowler/pull/9894)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -14,6 +14,7 @@ from prowler.lib.utils.utils import print_boxes
|
||||
from prowler.providers.cloudflare.exceptions.exceptions import (
|
||||
CloudflareCredentialsError,
|
||||
CloudflareIdentityError,
|
||||
CloudflareInvalidAccountError,
|
||||
CloudflareSessionError,
|
||||
)
|
||||
from prowler.providers.cloudflare.lib.mutelist.mutelist import CloudflareMutelist
|
||||
@@ -36,11 +37,13 @@ class CloudflareProvider(Provider):
|
||||
_fixer_config: dict
|
||||
_mutelist: CloudflareMutelist
|
||||
_filter_zones: set[str] | None
|
||||
_filter_accounts: set[str] | None
|
||||
audit_metadata: Audit_Metadata
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filter_zones: Iterable[str] | None = None,
|
||||
filter_accounts: Iterable[str] | None = None,
|
||||
config_path: str = None,
|
||||
config_content: dict | None = None,
|
||||
fixer_config: dict = {},
|
||||
@@ -74,6 +77,23 @@ class CloudflareProvider(Provider):
|
||||
# Store zone filter for filtering resources across services
|
||||
self._filter_zones = set(filter_zones) if filter_zones else None
|
||||
|
||||
# Store account filter and restrict audited_accounts accordingly
|
||||
self._filter_accounts = set(filter_accounts) if filter_accounts else None
|
||||
if self._filter_accounts:
|
||||
discovered_account_ids = {account.id for account in self._identity.accounts}
|
||||
invalid_accounts = self._filter_accounts - discovered_account_ids
|
||||
if invalid_accounts:
|
||||
invalid_str = ", ".join(sorted(invalid_accounts))
|
||||
raise CloudflareInvalidAccountError(
|
||||
file=os.path.basename(__file__),
|
||||
message=f"Account IDs not found: {invalid_str}.",
|
||||
)
|
||||
self._identity.audited_accounts = [
|
||||
account_id
|
||||
for account_id in self._identity.audited_accounts
|
||||
if account_id in self._filter_accounts
|
||||
]
|
||||
|
||||
Provider.set_global_provider(self)
|
||||
|
||||
@property
|
||||
@@ -105,6 +125,11 @@ class CloudflareProvider(Provider):
|
||||
"""Zone filter from --region argument to filter resources."""
|
||||
return self._filter_zones
|
||||
|
||||
@property
|
||||
def filter_accounts(self) -> set[str] | None:
|
||||
"""Account filter from --account-id argument to restrict scanned accounts."""
|
||||
return self._filter_accounts
|
||||
|
||||
@property
|
||||
def accounts(self) -> list[CloudflareAccount]:
|
||||
return self._identity.accounts
|
||||
@@ -248,10 +273,23 @@ class CloudflareProvider(Provider):
|
||||
if email:
|
||||
report_lines.append(f"Email: {Fore.YELLOW}{email}{Style.RESET_ALL}")
|
||||
|
||||
# Accounts
|
||||
if self.accounts:
|
||||
accounts = ", ".join([account.id for account in self.accounts])
|
||||
report_lines.append(f"Accounts: {Fore.YELLOW}{accounts}{Style.RESET_ALL}")
|
||||
# Audited accounts (only the ones that will actually be scanned)
|
||||
audited_accounts = self.identity.audited_accounts
|
||||
if audited_accounts:
|
||||
account_names = {
|
||||
account.id: account.name for account in self.identity.accounts
|
||||
}
|
||||
accounts_str = ", ".join(
|
||||
(
|
||||
f"{account_id} ({account_names[account_id]})"
|
||||
if account_id in account_names and account_names[account_id]
|
||||
else account_id
|
||||
)
|
||||
for account_id in audited_accounts
|
||||
)
|
||||
report_lines.append(
|
||||
f"Audited Accounts: {Fore.YELLOW}{accounts_str}{Style.RESET_ALL}"
|
||||
)
|
||||
|
||||
print_boxes(report_lines, report_title)
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@ def init_parser(self):
|
||||
)
|
||||
|
||||
scope_group = cloudflare_parser.add_argument_group("Scope")
|
||||
scope_group.add_argument(
|
||||
"--account-id",
|
||||
nargs="+",
|
||||
default=None,
|
||||
metavar="ACCOUNT_ID",
|
||||
help="Filter scan to specific Cloudflare account IDs. Only zones belonging to these accounts will be scanned.",
|
||||
)
|
||||
scope_group.add_argument(
|
||||
"--region",
|
||||
"--filter-region",
|
||||
|
||||
@@ -251,6 +251,7 @@ class Provider(ABC):
|
||||
elif "cloudflare" in provider_class_name.lower():
|
||||
provider_class(
|
||||
filter_zones=arguments.region,
|
||||
filter_accounts=arguments.account_id,
|
||||
config_path=arguments.config_file,
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
fixer_config=fixer_config,
|
||||
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider
|
||||
from prowler.providers.cloudflare.exceptions.exceptions import (
|
||||
CloudflareCredentialsError,
|
||||
CloudflareInvalidAccountError,
|
||||
)
|
||||
from prowler.providers.cloudflare.models import (
|
||||
CloudflareAccount,
|
||||
@@ -201,6 +202,74 @@ class TestCloudflareProvider:
|
||||
|
||||
assert provider.filter_zones == set(filter_zones)
|
||||
|
||||
def test_cloudflare_provider_with_filter_accounts(self):
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.cloudflare.cloudflare_provider.CloudflareProvider.setup_session",
|
||||
return_value=CloudflareSession(
|
||||
client=MagicMock(),
|
||||
api_token=API_TOKEN,
|
||||
api_key=None,
|
||||
api_email=None,
|
||||
),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.cloudflare.cloudflare_provider.CloudflareProvider.setup_identity",
|
||||
return_value=CloudflareIdentityInfo(
|
||||
user_id=USER_ID,
|
||||
email=USER_EMAIL,
|
||||
accounts=[
|
||||
CloudflareAccount(
|
||||
id=ACCOUNT_ID,
|
||||
name=ACCOUNT_NAME,
|
||||
type="standard",
|
||||
),
|
||||
CloudflareAccount(
|
||||
id="other-account-id",
|
||||
name="Other Account",
|
||||
type="standard",
|
||||
),
|
||||
],
|
||||
audited_accounts=[ACCOUNT_ID, "other-account-id"],
|
||||
),
|
||||
),
|
||||
):
|
||||
provider = CloudflareProvider(filter_accounts=[ACCOUNT_ID])
|
||||
|
||||
assert provider.filter_accounts == {ACCOUNT_ID}
|
||||
# Only the filtered account should remain in audited_accounts
|
||||
assert provider.identity.audited_accounts == [ACCOUNT_ID]
|
||||
|
||||
def test_cloudflare_provider_with_invalid_filter_accounts(self):
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.cloudflare.cloudflare_provider.CloudflareProvider.setup_session",
|
||||
return_value=CloudflareSession(
|
||||
client=MagicMock(),
|
||||
api_token=API_TOKEN,
|
||||
api_key=None,
|
||||
api_email=None,
|
||||
),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.cloudflare.cloudflare_provider.CloudflareProvider.setup_identity",
|
||||
return_value=CloudflareIdentityInfo(
|
||||
user_id=USER_ID,
|
||||
email=USER_EMAIL,
|
||||
accounts=[
|
||||
CloudflareAccount(
|
||||
id=ACCOUNT_ID,
|
||||
name=ACCOUNT_NAME,
|
||||
type="standard",
|
||||
),
|
||||
],
|
||||
audited_accounts=[ACCOUNT_ID],
|
||||
),
|
||||
),
|
||||
):
|
||||
with pytest.raises(CloudflareInvalidAccountError):
|
||||
CloudflareProvider(filter_accounts=["non-existent-account-id"])
|
||||
|
||||
def test_cloudflare_provider_properties(self):
|
||||
with (
|
||||
patch(
|
||||
|
||||
Reference in New Issue
Block a user