A validation harness that re-checks Prowler's GCP findings with independent evidence so you can separate real issues from false positives. For every Prowler check it runs up to five methods and reports each verdict side-by-side plus a consensus and a false-positive / false-negative flag.
| # | Method | What it does |
|---|---|---|
| 1 | API | A direct, single-resource read of the exact property (instances.get()), the authoritative live state. |
| 2 | Prowler replica | Re-implements Prowler's exact pass/fail rule, linked to the Prowler Hub page for the check. Tells you what Prowler should say even without a Prowler file. |
| 3 | Logs | Corroborates from Cloud Audit Logs — e.g. the last config-change entry that set a database flag. |
| 4 | Alternate | A different signal or API path (e.g. backupRuns.list() instead of the backup setting; assigned IPs instead of the ipv4Enabled flag). Divergence between two vantage points is itself a finding. |
| 5 | Exposure | For public-exposure checks only: actually tries to reach the resource from the internet (TCP/TLS probe). A successful connect is strong confirmation. |
Not every method applies to every check. A method that does not apply returns N/A with a reason rather than a fabricated verdict.
- Each applicable method returns
PASS/FAIL/N/A/MANUAL/ERROR. - Consensus = the combination of the definite methods (method 2 is excluded because it mirrors Prowler and would bias the comparison). Mixed signals lean conservative (a confirmed problem outweighs a clean read).
- Agreement compares consensus to the Prowler finding:
AGREE— verdicts match.LIKELY_FALSE_POSITIVE— Prowler says FAIL, our evidence says PASS. ← the main thing you're hunting.LIKELY_FALSE_NEGATIVE— Prowler says PASS, our evidence says FAIL.INCONCLUSIVE/NO_PROWLER_FINDING— not enough signal / nothing to compare.
cd gcp_validator
python -m venv .venv && . .venv/bin/activate # (Windows: .venv\Scripts\activate)
pip install -r requirements.txtThe tool only needs read-only access, and takes credentials three ways (first match wins):
# 1. Pass the service-account JSON key directly:
python run_all.py --key-file /path/to/sa-key.json --prowler out.ocsf.json
# (project defaults to the key's own project_id if you omit --project)
# 2. Or via the standard env var:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
# 3. Or user credentials from gcloud:
gcloud auth application-default loginRecommended read roles: roles/cloudsql.viewer, roles/logging.viewer
(add roles/viewer to cover the other services as they come online).
# All implemented checks, compared against a Prowler OCSF or CSV export:
python run_all.py --project MY_PROJECT --prowler prowler-output.ocsf.json -o report.xlsx
# Only Cloud SQL:
python run_all.py -p MY_PROJECT --service cloudsql --prowler out.ocsf.json
# Turn on the method-5 internet probe (makes real outbound connections):
python run_all.py -p MY_PROJECT --service cloudsql --enable-exposure-probe
# List what's implemented:
python run_all.py --list
# Run ONE control by itself (from the gcp_validator directory):
python -m checks.cloudsql.cloudsql_instance_public_access -p MY_PROJECT --prowler out.ocsf.json--prowler accepts either Prowler's OCSF JSON (*.ocsf.json) or a
CSV export. Without it, the tool still validates live GCP state and reports
each method's verdict (agreement column shows NO_PROWLER_FINDING).
A .xlsx with a Validation sheet (one row per resource per check) and a
Summary sheet. Columns: check id / service / severity / title / hub link,
resource identity, Prowler status + detail, each method's verdict + detail,
consensus, and the agreement flag (colour-coded). LIKELY_FALSE_POSITIVE
rows are highlighted amber.
Add --evidence-dir DIR (alias --screenshots DIR) to write a per-finding PNG
evidence card — the check, resource, every method's PASS/FAIL, the actual
config values read from the API, a UTC capture timestamp, and a Cloud Console
deep-link. The Excel also gains a hyperlinked Console Link column.
python run_all.py -p MY_PROJECT --prowler out.ocsf.json --evidence-dir ./evidence
# only the exceptions worth attaching to findings:
python run_all.py -p MY_PROJECT --prowler out.ocsf.json --evidence-dir ./evidence --evidence-findings-onlyImages are generated headless (Pillow) from data the tool already collected, so they carry provenance (API source + timestamp) suitable as audit evidence; the deep-link lets you also grab a live console screenshot if a reviewer wants one.
The Check Error column classifies operational problems (these are about your environment/credentials, not the checks):
AUTH SCOPE — … lacks the cloud-platform scope— the token can't call the admin APIs. This is common when running on a GCE VM / Cloud Shell whose service account has restricted access scopes (the requested scope is ignored there). Fix: run with a service-account key (--key-file key.json), orgcloud auth application-default loginas a user with thecloud-platformscope, or recreate the VM with--scopes=cloud-platform.PERMISSION DENIED — grant … roles/viewer— the scope is fine but the principal lacks IAM read access. Grant the SA/userroles/viewer(addroles/iam.securityReviewerfor full IAM policy reads, androles/cloudasset.viewerfor asset checks).SKIPPED — required API not enabled— that Google API isn't turned on in the project (e.g. API Keys API). Nothing to validate; enable the API only if you actually use it.TRANSIENT network error — re-run— a one-off socket/timeout; just re-run.
Recommended setup for a clean run:
# a dedicated read-only service account:
gcloud iam service-accounts create prowler-validator --project MY_PROJECT
gcloud projects add-iam-policy-binding MY_PROJECT \
--member="serviceAccount:prowler-validator@MY_PROJECT.iam.gserviceaccount.com" \
--role="roles/viewer"
gcloud projects add-iam-policy-binding MY_PROJECT \
--member="serviceAccount:prowler-validator@MY_PROJECT.iam.gserviceaccount.com" \
--role="roles/iam.securityReviewer"
gcloud iam service-accounts keys create key.json \
--iam-account="prowler-validator@MY_PROJECT.iam.gserviceaccount.com"
python run_all.py --key-file key.json --prowler out.ocsf.json -o report.xlsxAll 109 GCP checks across 16 services are implemented and plug into the same framework:
| Service | Checks | Service | Checks |
|---|---|---|---|
| compute | 31 | cloudsql | 24 |
| iam | 12 | cloudstorage | 10 |
| logging | 10 | apikeys | 4 |
| dns | 3 | bigquery | 3 |
| kms | 3 | cloudfunction | 2 |
| secretmanager | 2 | dataproc | 1 |
| artifacts | 1 | gemini | 1 |
| gke | 1 | gcr | 1 |
A few controls cannot be validated purely from project-scoped, read-only API
calls. These are implemented honestly — they run what they can and mark the
rest MANUAL / N/A with a reason rather than guessing:
compute_public_address_shodan— needsSHODAN_API_KEYfor methods 1/2; method 5 still probes ports directly.cloudstorage_uses_vpc_service_controls,iam_organization_essential_contacts_configured— VPC-SC perimeters and Essential Contacts are org-level; project creds see only a partial picture (reportedMANUAL/ best-effort proxy).iam_service_account_unused,iam_sa_user_managed_key_unused— "unused" needs last-authentication data, so method 3 (Cloud Audit Logs) is the real signal; method 1 reportsMANUAL.logging_log_metric_filter_and_alert_*— matched by filter-substring heuristics against your log-based metrics + Monitoring alert policies; verify the pattern against your naming if a result looks off.gemini_api_disabled, other service-enablement checks — asserted via the Service Usage API; confirm the pass direction on the hub page for your Prowler version.
Method-specific notes and the exact predicate live in each check file's docstring, alongside its Prowler Hub link.
- Create
checks/<service>/<check_id>.py. - Subclass
gcpval.base_check.BaseCheck(or a service base likechecks.cloudsql._base.CloudSqlCheck), setcheck_id, implementdiscover_resources()and whichever ofapi_check/prowler_replica_check/logs_check/alternate_check/exposure_checkapply. - That's it —
run_all.pydiscovers it automatically, andpython -m checks.<service>.<check_id>runs it standalone.