feat(sdk): add sdk_only provider property to hide providers from the app#11578
feat(sdk): add sdk_only provider property to hide providers from the app#11578StylusFrost wants to merge 91 commits into
Conversation
…very - Implemented dynamic loading of external providers via entry points, allowing for greater flexibility in provider integration. - Added functionality to discover compliance directories from entry points, enabling external compliance frameworks to be loaded seamlessly. - Refactored check module resolution to prioritize built-in checks while falling back to entry points if necessary. - Improved compliance framework loading to include both built-in and external sources, ensuring comprehensive compliance coverage. - Enhanced CLI argument parsing to support external providers, improving user experience and configurability. - Introduced extensive unit tests to validate dynamic loading, compliance discovery, and overall integration of external providers.
…iders and add tests
…ific compliance rendering
…iders load_and_validate_config_file only detected the namespaced format for 5 hardcoded providers (aws, gcp, azure, kubernetes, m365). For every other built-in (github, nhn, vercel, cloudflare, iac, llm, image, mongodbatlas, oraclecloud, openstack, alibabacloud, googleworkspace) and for any external plug-in, the full YAML was returned wrapped instead of the provider's own block. Replace the hardcoded list with a dynamic check: if the file has a top-level key matching the provider and its value is a dict, unwrap it. Keep the legacy flat format for AWS only (historical, pre-multicloud) and identify it by the absence of nested-dict top-level values, which prevents cross-provider config leakage when a namespaced file has no section for the requested provider.
…etadata validators
…r clearer import errors
…covery Conflict in prowler/config/config.py resolved by combining both branches: - HEAD: external compliance discovery via entry points (PROWLER-1391) - master: multi-provider framework JSONs scanned at top-level compliance/ (#10300) Order: built-in per-provider -> built-in multi-provider -> external entry points. Built-ins first so they win on name collisions against external registrations. Supporting external plug-ins to register multi-provider frameworks is tracked in PROWLER-1444.
…r names with hyphens are not misparsed
…er-contract-dynamic-discovery # Conflicts: # prowler/config/config.py
…very
Calling importlib.util.find_spec on prowler.providers.{provider}.services
for an external provider propagates ModuleNotFoundError when the parent
package prowler.providers.{provider} does not exist, instead of returning
None. This caused recover_checks_from_provider, _resolve_check_module and
Scan.scan to fail with "No module named 'prowler.providers.{external}'"
even though the plug-in registered its checks via entry points correctly.
Gate the built-in branch on Provider.is_builtin (which already wraps the
find_spec in try/except) and reuse _resolve_check_module from Scan.scan
so external providers fall through to the entry-point lookup.
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
|
✅ All necessary |
🔒 Container Security ScanImage: ✅ No Vulnerabilities DetectedThe container image passed all security checks. No known CVEs were found.📋 Resources:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@prowler/providers/common/provider.py`:
- Around line 240-241: The condition at the mutelist_content assignment uses a
truthy check that will drop empty dictionaries since they evaluate to False in
Python. This prevents `mutelist_content={}` from being passed to kwargs, which
can unintentionally trigger provider-side default mutelist loading. Replace the
truthy check `if mutelist_content:` with an explicit None check `if
mutelist_content is not None:` so that empty dictionary payloads are preserved
and included in kwargs.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8a101223-f338-42ae-94eb-bce163999a1a
📒 Files selected for processing (21)
prowler/CHANGELOG.mdprowler/config/config.pyprowler/providers/alibabacloud/alibabacloud_provider.pyprowler/providers/aws/aws_provider.pyprowler/providers/azure/azure_provider.pyprowler/providers/cloudflare/cloudflare_provider.pyprowler/providers/common/provider.pyprowler/providers/gcp/gcp_provider.pyprowler/providers/github/github_provider.pyprowler/providers/googleworkspace/googleworkspace_provider.pyprowler/providers/iac/iac_provider.pyprowler/providers/image/image_provider.pyprowler/providers/kubernetes/kubernetes_provider.pyprowler/providers/m365/m365_provider.pyprowler/providers/mongodbatlas/mongodbatlas_provider.pyprowler/providers/okta/okta_provider.pyprowler/providers/openstack/openstack_provider.pyprowler/providers/oraclecloud/oraclecloud_provider.pyprowler/providers/vercel/vercel_provider.pytests/config/config_test.pytests/providers/external/test_dynamic_provider_loading.py
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #11578 +/- ##
===========================================
- Coverage 94.11% 79.22% -14.90%
===========================================
Files 247 1878 +1631
Lines 36408 63187 +26779
===========================================
+ Hits 34265 50058 +15793
- Misses 2143 13129 +10986 Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. The branch was updated while autofix was in progress. Please try again. |
Use an explicit None check so an empty mutelist_content dict is passed through instead of being dropped, which could trigger provider-side default mutelist loading.
danibarranqueroo
left a comment
There was a problem hiding this comment.
Linode was added as new provider, since it's sdk_only I think that is not needed to add that flag into the provider code, but please review it. Thanks!
|
Confirmed, nothing to add for Linode. Every provider defaults to |
Context
With dynamic provider discovery, the API needs the SDK to be the single source of truth for which providers exist, how they are constructed from stored credentials, and which ones belong in the app. This PR collects the SDK contract changes the API layer consumes; the API wiring itself ships separately.
Scope
Provider.sdk_onlycontract property (defaultTrue),Provider.get_app_providers(),sdk_only = Falseon app-facing built-in providers_get_ep_compliance_dirsreturns{provider: [paths]}) instead of overwritingProvider.get_scan_arguments(),Provider.get_connection_arguments()andProvider.get_credentials_schema()contract methodsAutonomy
Description
sdk_onlyprovider propertyProvider.sdk_only: bool = Trueon the base contract: a provider (built-in or external) is CLI/SDK-only and hidden from the app until it opts in withsdk_only = False.Provider.get_app_providers(): returns the providers withsdk_only = False(counterpart ofget_available_providers(), which keeps listing everything for the CLI). A provider whose class fails to import is treated assdk_only.sdk_only = False, so the released behavior is preserved;llm,nhn,scaleway,stackitkeep the default and stay hidden.Multi-package compliance discovery
_get_ep_compliance_dirsnow accumulates every directory per provider ({provider: [paths]}) so a provider contributed by several external packages has all its compliance frameworks discovered instead of the last one overwriting the rest.Programmatic construction contract
Provider.get_scan_arguments(provider_uid, secret, mutelist_content=None)builds the constructor kwargs from a stored uid plus a secret dict (the shape the API persists), as opposed to the CLI's explicit flags.Provider.get_connection_arguments(provider_uid, secret)builds thetest_connectionkwargs, which often differ in shape from the constructor.Provider.get_credentials_schema()maps each accepted secret type to the pydantic model that validates it; the default is empty (secret accepted as-is and validated bytest_connection).These base implementations are the defaults; providers override them as needed. The CLI is unaffected. No
api/changes are included in this PR by design.Steps to review
Checklist
SDK/CLI
License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests