This check flags GCP firewall rules that permit SSH (port 22) from 0.0.0.0/0, exposing your VMs to the entire internet. Restrict the source range to trusted CIDRs or, better yet, move SSH access behind Identity-Aware Proxy.
An open SSH port facing the public internet is one of the most common and most exploited misconfigurations in cloud networking. The Firewall Allows Public SSH check (vpc_openssh) inspects your GCP VPC firewall rules and raises a finding whenever a rule allows inbound traffic on TCP port 22 from a public source such as 0.0.0.0/0.
It sounds harmless. You spun up a VM, needed to log in, and opened port 22 to the world to get unblocked quickly. But that single rule turns your instance into a target for the constant background scanning that hits every public IP on the internet within minutes of it going live.
What this check detects
The check parses every ingress firewall rule in your VPC networks and looks for rules that match all of the following conditions:
- The rule has an ingress direction and an allow action.
- The allowed protocol/port includes TCP 22, either explicitly, as part of a range, or because the rule allows all ports.
- The source range includes a public CIDR such as
0.0.0.0/0or any range broad enough to cover untrusted internet addresses.
When all three line up, the rule is exposing SSH to the public internet, and the check fails.
Note: GCP firewall rules apply to instances by network tag, service account, or to all instances in the network. A rule that targets all instances with 0.0.0.0/0 on port 22 affects every VM in that VPC, not just the one you were trying to reach.
Why it matters
A public SSH port is an open invitation for automated attacks. The risk is not theoretical.
Constant credential brute-forcing
Bots scan the IPv4 space continuously. Once they find an open port 22, they start guessing usernames and passwords. If your instance allows password authentication (or has a weak key setup), it is only a matter of time. Logs from any internet-facing SSH host show thousands of login attempts per day from a rotating set of IPs.
Exploitation of unpatched SSH or OS vulnerabilities
Brute force is not the only path. Vulnerabilities in OpenSSH or the underlying OS can be exploited directly when the port is reachable. A public port 22 means a newly disclosed CVE becomes an immediate problem for you, not a problem you have time to patch on your own schedule.
Lateral movement and blast radius
If an attacker gets a foothold on one VM, the service account attached to that instance often has access to other GCP resources. Cloud Storage buckets, secrets, internal services. An exposed SSH host can become the entry point for a much larger breach.
Warning: The default default-allow-ssh rule that GCP creates in the default VPC opens port 22 from 0.0.0.0/0. If you have never touched your firewall rules, you are very likely already exposed.
How to fix it
The fix depends on how your team actually needs to reach these instances. Three approaches, ordered from quickest to most robust.
Option 1: Restrict the source range
If you have a known set of office or VPN IPs that need SSH, tighten the source range to those CIDRs. First, find the offending rule:
gcloud compute firewall-rules list \
--filter="direction=INGRESS AND allowed[].ports:22" \
--format="table(name, network, sourceRanges.list(), allowed[].map().firewall_rule().list())"
Then update the rule to allow only your trusted ranges:
gcloud compute firewall-rules update default-allow-ssh \
--source-ranges="203.0.113.10/32,198.51.100.0/24"
Danger: Updating a firewall rule takes effect immediately. If you remove the range your current session is connecting from, you can lock yourself out of the instance. Confirm your own egress IP is included before you run the update.
Option 2: Use Identity-Aware Proxy (recommended)
The cleanest solution is to stop exposing port 22 publicly at all and route SSH through Identity-Aware Proxy (IAP). IAP authenticates the user with Google identity and IAM before any traffic reaches the VM, so the port is never open to the raw internet.
First, remove the public rule and replace it with one that only allows IAP's TCP forwarding range:
# Delete the public SSH rule
gcloud compute firewall-rules delete default-allow-ssh
# Allow SSH only from the IAP forwarding range
gcloud compute firewall-rules create allow-ssh-from-iap \
--direction=INGRESS \
--action=ALLOW \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--network=default
Grant the right people the IAP-secured tunnel role:
gcloud projects add-iam-policy-binding MY_PROJECT \
--member="user:[email protected]" \
--role="roles/iap.tunnelResourceAccessor"
Now connect through IAP, with no public port required:
gcloud compute ssh my-instance \
--zone=us-central1-a \
--tunnel-through-iap
Tip: The range 35.235.240.0/20 is the fixed IAP TCP forwarding source range across all GCP projects. You can hardcode it safely.
Option 3: Fix it in Terraform
If your firewall rules live in Terraform, do not patch them by hand. That just causes drift. Update the source range in code and apply:
resource "google_compute_firewall" "allow_ssh_iap" {
name = "allow-ssh-from-iap"
network = google_compute_network.main.name
direction = "INGRESS"
allow {
protocol = "tcp"
ports = ["22"]
}
# IAP TCP forwarding range only, never 0.0.0.0/0
source_ranges = ["35.235.240.0/20"]
target_tags = ["ssh-access"]
}
terraform plan
terraform apply
How to prevent it from happening again
Fixing one rule is easy. Keeping new ones from creeping in is the real work. A few controls catch this before it ever reaches production.
Organization Policy constraints
GCP ships with an org policy that blocks the creation of firewall rules allowing ingress from broad ranges. Apply constraints/compute.restrictVpcPeering is not the one here, instead use the firewall policy constraints and the more targeted approach below.
gcloud resource-manager org-policies enable-enforce \
compute.disableDefaultNetworkCreation \
--organization=ORGANIZATION_ID
Disabling default network creation removes the auto-generated default-allow-ssh rule, which is one of the most common sources of this finding.
Policy-as-code in CI/CD
Catch the bad rule at pull request time. With Open Policy Agent and conftest, you can fail the build whenever a plan tries to open port 22 to the world:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_compute_firewall"
rule := resource.change.after
rule.source_ranges[_] == "0.0.0.0/0"
allow := rule.allow[_]
allow.ports[_] == "22"
msg := sprintf("Firewall '%s' opens SSH to 0.0.0.0/0", [resource.name])
}
Wire it into your pipeline against the Terraform plan output:
terraform show -json tfplan.binary > plan.json
conftest test plan.json
Tip: Run the vpc_openssh check in Lensix on a schedule so any rule that slips through code review (or gets created manually in the console) is flagged within minutes instead of sitting open for weeks.
Best practices
- Default to IAP or a bastion. Treat public SSH as something you almost never need. IAP gives you IAM-based access with full audit logging and no open port.
- Never use
0.0.0.0/0on management ports. This applies to SSH (22), RDP (3389), and database ports too. Scope every rule to the narrowest source range that works. - Use network tags or service accounts to target rules. Avoid rules that apply to all instances in a VPC. Tag the specific instances that need access.
- Audit your default VPC. The default network and its permissive rules are a recurring source of exposure. Delete the default network in projects that do not need it.
- Log and alert on firewall changes. Send Compute Engine admin activity logs to a sink and alert when a rule with a public source on port 22 is created.
- Prefer ephemeral access over standing access. Grant SSH access for the duration of a task, then revoke it, rather than leaving broad rules in place indefinitely.
The goal is not to make SSH harder to use, it is to make exposure deliberate. If a port 22 rule is open to the internet, someone should have made a conscious decision to do that, and it should be visible in your audit trail.
An open SSH port is cheap to fix and expensive to ignore. Tighten the source range, push access behind IAP, and put a policy gate in front of your firewall changes so the problem cannot quietly return.

