This check flags GCP firewall rules that allow inbound DNS (port 53) from 0.0.0.0/0. An exposed DNS service can be abused for amplification attacks, data exfiltration, and reconnaissance. Restrict the source range to known internal CIDRs or remove the rule entirely.
DNS is one of those protocols that feels harmless because it is everywhere. It resolves names to addresses, it works quietly in the background, and most teams never think twice about it. That familiarity is exactly why an open DNS port slips past reviews. The Firewall Allows Public DNS check (vpc_opendns) catches GCP VPC firewall rules that permit traffic on port 53 from any public source, and it is worth paying attention to.
What this check detects
The check inspects your GCP VPC firewall rules and flags any ingress rule that:
- Allows traffic on TCP or UDP port 53, and
- Has a source range of
0.0.0.0/0(or any other public, unbounded CIDR).
In practice that means a rule that looks something like this:
{
"name": "allow-dns-anywhere",
"direction": "INGRESS",
"sourceRanges": ["0.0.0.0/0"],
"allowed": [
{ "IPProtocol": "udp", "ports": ["53"] },
{ "IPProtocol": "tcp", "ports": ["53"] }
]
}
Note: GCP firewall rules are stateful and apply at the VPC network level, targeting instances by network tags or service accounts. A single permissive rule can expose every instance carrying the matching tag, not just one machine.
DNS uses UDP 53 for most queries and falls back to TCP 53 for larger responses and zone transfers. Both deserve scrutiny, but UDP is the bigger concern because it is connectionless and trivially spoofable.
Why it matters
An open DNS port is not a theoretical problem. Depending on what is actually listening behind that rule, you are exposed to several concrete attacks.
DNS amplification and reflection attacks
If you are running a resolver that answers queries from anyone, attackers can use it as a weapon against third parties. They send small spoofed queries with your server's address as the source, and the much larger responses get reflected at a victim. A 60 byte query can trigger a multi-kilobyte response, giving attackers a force multiplier of 50x or more. Your bandwidth pays for someone else's DDoS, and your IP can end up on abuse blocklists.
Data exfiltration through DNS tunneling
DNS is a favorite covert channel because it is rarely inspected. Malware on a compromised instance can encode stolen data into DNS queries and ship it out through an open resolver. If your firewall allows arbitrary DNS in and out, you have handed attackers a quiet pipe that bypasses most egress controls.
Reconnaissance and zone transfers
If TCP 53 is open to the world and a misconfigured authoritative server allows zone transfers (AXFR), an attacker can dump your entire DNS zone. That hands them a map of your internal hostnames, mail servers, and service endpoints in a single request.
Warning: Even if no DNS service is currently listening, an open port 53 is a latent risk. The moment someone deploys a resolver or a misbehaving container binds to that port, the exposure becomes real with no firewall change needed.
How to fix it
The fix depends on whether the rule serves a real purpose. Most public DNS rules are accidental, left over from testing, or copied from an overly broad template.
Step 1: Find the offending rule
gcloud compute firewall-rules list \
--filter="allowed.ports=53 AND sourceRanges=0.0.0.0/0" \
--format="table(name, network, sourceRanges.list(), allowed[].map().firewall_rule().list())"
Inspect the full rule before changing anything so you know what it targets:
gcloud compute firewall-rules describe allow-dns-anywhere
Step 2: Decide what the rule should actually allow
Ask who legitimately needs to reach this service on port 53. In almost every case the answer is internal VPC subnets or a short list of known IPs, never the entire internet.
Step 3a: Restrict the source range
If the DNS service is needed internally, narrow the source to your VPC CIDR ranges instead of the public internet.
gcloud compute firewall-rules update allow-dns-anywhere \
--source-ranges="10.0.0.0/8,172.16.0.0/12"
Step 3b: Delete the rule entirely
If nothing legitimate depends on it, remove it.
Danger: Deleting a firewall rule takes effect immediately and can break live traffic. Confirm no internal service relies on this rule before running the command below. Check VPC Flow Logs for active connections on port 53 first.
gcloud compute firewall-rules delete allow-dns-anywhere
Step 4: Verify the change
gcloud compute firewall-rules list \
--filter="allowed.ports=53 AND sourceRanges=0.0.0.0/0"
An empty result means no public DNS rules remain in that project.
Tip: For most workloads you do not need to run your own resolver at all. GCP provides Cloud DNS and an internal metadata resolver at 169.254.169.254 that handles instance name resolution without any inbound firewall exposure.
Fixing it in infrastructure as code
If your firewall rules live in Terraform, patch the source range there so the fix survives the next apply. A bad rule looks like this:
resource "google_compute_firewall" "dns" {
name = "allow-dns-anywhere"
network = google_compute_network.main.name
allow {
protocol = "udp"
ports = ["53"]
}
source_ranges = ["0.0.0.0/0"] # too broad
}
The corrected version scopes the source to internal ranges and applies the rule only to tagged DNS instances:
resource "google_compute_firewall" "dns" {
name = "allow-dns-internal"
network = google_compute_network.main.name
allow {
protocol = "udp"
ports = ["53"]
}
allow {
protocol = "tcp"
ports = ["53"]
}
source_ranges = ["10.0.0.0/8"]
target_tags = ["dns-server"]
}
How to prevent it from happening again
One-time fixes do not last. The same rule tends to reappear from a copied module or a quick console change during an incident. Build guardrails so a public port 53 cannot ship.
Policy as code with OPA
If you run Terraform through CI, add a Conftest or OPA policy that fails the build when a firewall rule combines port 53 with a public source.
package firewall
deny[msg] {
rule := input.resource.google_compute_firewall[name]
rule.source_ranges[_] == "0.0.0.0/0"
allow := rule.allow[_]
allow.ports[_] == "53"
msg := sprintf("Firewall '%s' exposes DNS port 53 to the public internet", [name])
}
Organization policy constraints
At the org level you can use a hierarchical firewall policy to deny ingress on sensitive ports across all projects, so a single project owner cannot punch a hole on their own.
gcloud compute firewall-policies rules create 1000 \
--firewall-policy=org-baseline \
--direction=INGRESS \
--action=deny \
--layer4-configs=udp:53,tcp:53 \
--src-ip-ranges=0.0.0.0/0 \
--organization=YOUR_ORG_ID
Tip: Run the Lensix vpc_opendns check on a schedule against every project. Catching drift within minutes of a manual console change is far cheaper than finding it after your IP lands on an abuse list.
Best practices
- Default to deny. Start with no public ingress and open only what a documented requirement demands.
- Never use
0.0.0.0/0for service ports. Public source ranges belong on load balancers and edge entry points, not on internal services like DNS, databases, or management ports. - Use target tags or service accounts. Scope firewall rules to specific workloads instead of applying them network wide.
- Disable zone transfers on any authoritative DNS server unless a secondary explicitly needs them, and restrict AXFR to known secondaries by IP.
- Enable VPC Flow Logs on subnets running DNS so you can spot unusual query volume that signals amplification abuse or tunneling.
- Prefer managed DNS. Cloud DNS removes the need to expose your own resolver in most architectures.
- Audit firewall rules regularly. Rules accumulate. Schedule a recurring review and let automated checks flag drift between reviews.
DNS earns its reputation as boring infrastructure right up until it becomes the path an attacker uses. Closing port 53 to the public internet is a small change with an outsized payoff, and with policy as code in place it stays closed for good.

