Static Analysis
Static analysis catches the bugs validate cannot — a publicly readable bucket, a 0.0.0.0/0 firewall rule, a disk with no customer-managed key — by inspecting your HCL against a rule set before any apply. validate proved the config is well-formed; these tools prove it is not dangerous. They read the same files and reach a different verdict.
Three tools cover GCP. tflint with the google ruleset handles provider-specific correctness and deprecations; tfsec (now folded into Trivy) and checkov handle security and misconfiguration scanning. All three are fast, credential-free, and run right after validate in the same cheap offline gate — they never touch Google Cloud.
tflint and the google Ruleset
tflint flags invalid google provider usage that validate misses: deprecated arguments scheduled for removal, machine types or regions that do not exist, unenforced naming conventions, and declarations you wrote but never referenced. The base tflint binary is provider-agnostic; the GCP awareness comes from the tflint-ruleset-google plugin, which carries the rules that know e2-medium is real and e2-mediumm is a typo.
The line against validate is this: validate checks that an argument exists in the schema and has the right type; tflint checks that its value makes sense. A region string that is syntactically a string but names no real GCP region passes validate and fails tflint.
# .tflint.hcl — enable the GCP-aware plugin plugin "google" { enabled = true version = "0.31.0" source = "github.com/terraform-linters/tflint-ruleset-google" }
With the plugin pinned to a version, tflint --init downloads it and tflint --recursive runs it across the root module and every submodule. Pinning the version matters: an unpinned plugin can pull a new rule release that turns a green pipeline red without any change to your code.
tfsec / Trivy
tfsec — now distributed inside Trivy — is a security-focused scanner with hundreds of built-in GCP rules. It reads your HCL and flags a google_storage_bucket made public, a google_compute_firewall allowing ingress from 0.0.0.0/0, disks and Cloud SQL instances without CMEK, and over-broad IAM bindings. Every one of those is a finding produced from the source, before a single resource exists.
checkov
checkov (from Bridgecrew, now Prisma Cloud) is a policy-as-code scanner with a large GCP rule library and support for custom policies in Python or YAML. It overlaps with tfsec on coverage and adds graph-based checks across resources — so a bucket and a separate google_storage_bucket_iam_member that together make data public can be evaluated as a pair, not just one resource at a time. Run both and you get broad coverage plus the cross-resource view.
The GCP Misconfigs They Catch
The findings cluster into a recurring GCP exposure list. Public buckets — an allUsers or allAuthenticatedUsers principal in an IAM member. Default-allow firewall rules that open a port to the whole internet. Missing customer-managed encryption keys on disks, Cloud SQL, and BigQuery where policy requires them. Public Cloud SQL instances with an external IP. Unrestricted SSH on port 22 from 0.0.0.0/0. These five are the ones that surface again and again.
The example below is exactly the kind of config that passes validate cleanly and is the reason scanners exist. The IAM member grants the world read access to the bucket, and the firewall opens SSH to the entire internet — both well-formed, both wrong.
resource "google_storage_bucket_iam_member" "public" { bucket = google_storage_bucket.raw.name role = "roles/storage.objectViewer" member = "allUsers" # public read — tfsec & checkov flag } resource "google_compute_firewall" "ssh" { name = "allow-ssh" network = google_compute_network.vpc.id source_ranges = ["0.0.0.0/0"] # SSH from the whole internet allow { protocol = "tcp" ports = ["22"] } }
A scanner fails the build on both. The fix is to scope the member to a real principal and the firewall to a known source range or, better, replace direct SSH with Identity-Aware Proxy — but the point here is that the gate catches the exposure before apply, when it costs nothing.
Wiring Into CI
Run the scanners after validate and before plan, in the offline credential-free stage, and fail the build on high-severity findings. They need no GCP access, so they fit alongside validate in the cheap gate. The job is to stop a public bucket from ever reaching a plan, let alone an apply.
Real configs occasionally need a documented exception — a bucket that is genuinely meant to serve public assets. Handle it with a narrow inline ignore (#tfsec:ignore:google-storage-bucket-not-public) on the specific resource, with a comment naming the reason, never a blanket baseline that suppresses a whole rule. The moment everything is suppressed, the gate is theater and the next real public bucket sails straight through.
tflint (with the google ruleset) — correctness and deprecation: invalid machine types and regions, deprecated arguments, unused declarations, naming. Catches mistakes that are wrong, not necessarily insecure. Choose it to keep the config valid against the real provider.
tfsec / Trivy and checkov — security and misconfiguration: public buckets, open firewalls, missing CMEK, over-broad IAM. Catches exposure. checkov adds graph-based cross-resource checks. Run one or both alongside tflint; they answer different questions.
- Relying on
validatefor security and shipping agoogle_storage_bucket_iam_membergrantingallUserstheobjectViewerrole — validate passes a public bucket cleanly; only a scanner flags it. - Leaving a
google_compute_firewallwithsource_ranges = ["0.0.0.0/0"]on port 22 — a default-allow-the-internet rule no config check catches, but tfsec and checkov both flag immediately. - Suppressing scanner findings wholesale with a broad baseline instead of triaging them — once everything is suppressed, the gate is theater and the next real public bucket goes through.
- Provisioning disks, Cloud SQL, or BigQuery without CMEK where policy requires it — scanners catch the missing
kms_key_namebefore the resource exists, which is far cheaper than after. - Leaving the
tflint-ruleset-googleplugin or the scanner version unpinned, so a new rule release silently turns a passing pipeline red mid-sprint with no code change. - Treating
tflintandtfsecas interchangeable and running only one — tflint misses public buckets, the security scanners miss invalid machine types; they cover different ground.
- Run
tflintwithtflint-ruleset-googleplus one security scanner — tfsec/Trivy or checkov — in CI, failing the build on high-severity findings. - Scan for the GCP exposure set explicitly — public buckets,
0.0.0.0/0firewall ingress, missing CMEK, public database IPs, open SSH — and treat each as build-breaking. - Suppress a finding only with an inline ignore comment that names the reason on the specific resource, never with a blanket baseline that hides future regressions.
- Pin both the scanner and the ruleset versions so a new rule release does not silently turn a green pipeline red mid-sprint.
- Run the scanners in the offline credential-free stage, after
validateand beforeplan, so exposure is caught before anything reaches GCP.
Knowledge Check
What does tflint with the google ruleset catch that validate cannot?
- An argument whose value is invalid — a machine type or region that does not exist — even though it is the right type
- A cross-resource attribute reference that points to a resource declared nowhere in the entire configuration, breaking the dependency graph
- A required argument that was accidentally omitted from a resource block in the config
- A globally duplicate bucket name that is already owned by a different GCP project
Why does a public bucket pass validate but fail a security scanner?
- Granting
allUsersread access is a well-formed, type-correct config; only a scanner judges it as exposure - validate cannot parse
google_storage_bucket_iammember blocks, so it silently skips them entirely - The security scanner calls the live GCP API to confirm the bucket is actually publicly readable
- validate only runs after apply has finished, by which point the public bucket already physically exists in GCP
What distinguishes tflint from tfsec and checkov?
- tflint focuses on provider correctness and deprecations; tfsec and checkov focus on security and misconfiguration
- tflint scans live deployed cloud resources for drift while tfsec and checkov scan only the HCL source
- tflint needs live GCP credentials to run while the security scanners tfsec and checkov both run completely offline
- tflint enforces policy server-side at the GCP API while the others only gate the resolved plan in CI
What is the risk of suppressing scanner findings with a blanket baseline?
- A whole rule is silenced, so the next genuine violation of that rule passes the gate unnoticed
- A large baseline slows the scanner down enough to repeatedly time out the CI job
- A baseline silently forces every existing finding up to high severity, breaking the build
- Baselines are evaluated against live GCP state, so they require valid cloud credentials to run
You got correct