Back to blog
Best PracticesCloud SecurityGCPNetworkingOperations & Compliance

Firewall Allows Public SaltStack: Locking Down GCP Ports 4505 and 4506

Learn why exposing SaltStack ports 4505/4506 to the internet on GCP is a critical RCE risk, and how to fix and prevent it with CLI, Terraform, and policy as code.

TL;DR

This check flags GCP firewall rules that expose SaltStack ports 4505 and 4506 to 0.0.0.0/0. An open Salt master is a direct path to remote code execution across every minion you manage, so lock these ports to trusted source ranges or remove the rule entirely.

SaltStack is one of those tools that quietly runs a large chunk of someone's infrastructure. When it is configured well, it is a powerful way to manage configuration and orchestration at scale. When its control ports are reachable from the public internet, it becomes one of the fastest routes from "internet scan" to "attacker owns your whole fleet."

The Lensix check vpc_opensalt looks at your GCP VPC firewall rules and raises a finding whenever a rule allows inbound traffic to TCP ports 4505 or 4506 from a public source range. This post explains what those ports do, why open exposure is dangerous, and how to fix and prevent it.


What this check detects

SaltStack uses two TCP ports for communication between the Salt master and its minions:

  • 4505 — the publish port. The master pushes commands to minions over this port.
  • 4506 — the request port. Minions send results back and the master serves file and data requests here.

The vpc_opensalt check fires when a GCP firewall rule meets all of these conditions:

  • Direction is INGRESS
  • Action is allow
  • The protocol/port list includes TCP 4505 or 4506 (directly, in a range, or via "all ports")
  • The source range includes 0.0.0.0/0 or another broad public block

Note: GCP firewall rules apply at the VPC network level and attach to instances by network tags or service accounts. A single overly broad rule can expose many instances at once, even ones you did not intend to manage with Salt.


Why it matters

The Salt master is, by design, an authority that can run arbitrary commands as root on every connected minion. That is the entire point of the tool. So anything that can talk to the master, or impersonate it, inherits enormous power.

This is not a theoretical concern. In 2020, two CVEs in Salt (CVE-2020-11651, an authentication bypass, and CVE-2020-11652, a directory traversal) were chained to achieve unauthenticated remote code execution on exposed Salt masters. Within days of disclosure, internet-wide scanners were hitting port 4506 and compromising thousands of hosts. Victims included LineageOS, Ghost, and DigiCert, among others. Many of those masters were popped specifically because ports 4505/4506 were reachable from the open internet.

The attack pattern is simple:

  1. An attacker scans for hosts listening on 4506.
  2. They exploit a master vulnerability or weak authentication to send a command.
  3. The command runs as root on every minion attached to that master.

From there it is ransomware, cryptominers, credential theft, or lateral movement, depending on the attacker's goals. Because Salt already has trusted access into the rest of your environment, the blast radius is your entire managed fleet rather than a single host.

Danger: An exposed Salt master is effectively a remote root shell on every machine it controls. Treat a public 4505/4506 finding as a critical issue and remediate it before you do anything else on this list.


How to fix it

The goal is to make sure 4505 and 4506 are only reachable from the hosts that legitimately need them: your minions and your administrators, over private networking or a VPN. Start by finding the offending rule.

1. Identify the open rule

gcloud compute firewall-rules list \
  --filter="direction=INGRESS AND allowed[].ports~'4505|4506'" \
  --format="table(name,network,sourceRanges.list(),allowed[].map().firewall_rule().list(),targetTags.list())"

Inspect any rule whose sourceRanges includes 0.0.0.0/0. Pull the full definition so you know exactly what you are changing:

gcloud compute firewall-rules describe RULE_NAME --format=yaml

2. Decide on the correct source

You have three common options, in rough order of preference:

  • Internal only: restrict to your VPC subnet ranges so only private instances reach the master.
  • Specific admin ranges: allow your office, bastion, or VPN egress CIDRs.
  • Remove entirely: if Salt traffic never needs to cross the firewall (for example, master and minions live in the same subnet with default intra-VPC connectivity), delete the rule.

3. Tighten the source range

Update the existing rule to replace the public range with a trusted one:

gcloud compute firewall-rules update RULE_NAME \
  --source-ranges=10.0.0.0/16,203.0.113.10/32

Warning: Changing source ranges can break connectivity for minions whose source IPs are not in the new list. Before applying, confirm where your minions actually connect from. If they reach the master over a NAT gateway or an external IP, that egress address must be in the allowed ranges or your fleet will silently stop checking in.

4. Or remove the rule outright

Danger: Deleting a firewall rule is immediate and affects all instances it targets. Verify the rule is not the only path your minions use to reach the master before running this.

gcloud compute firewall-rules delete RULE_NAME

5. Verify

Re-run the list command and confirm no rule allows 4505/4506 from a public source. From an external host, confirm the ports no longer respond:

# From outside your VPC, this should now time out or be refused
nc -vz MASTER_PUBLIC_IP 4506

Tip: The most robust fix is to give the Salt master no public IP at all. Run it on an internal-only instance and have admins reach it through a bastion or Identity-Aware Proxy. If the master is not internet-routable, an accidental open firewall rule cannot expose it.


Express the fix as infrastructure as code

Clicking around the console or running one-off gcloud commands fixes today's problem but invites tomorrow's. Define the rule in IaC so the source range is reviewed and version controlled.

Terraform example for a properly scoped Salt rule:

resource "google_compute_firewall" "salt_internal" {
  name    = "allow-salt-internal"
  network = google_compute_network.main.name

  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["4505", "4506"]
  }

  # Only the VPC subnet and the admin VPN egress
  source_ranges = ["10.0.0.0/16", "203.0.113.10/32"]
  target_tags   = ["salt-master"]
}

Keeping source_ranges in the repo means any future change to that list goes through code review, where a reviewer can catch a 0.0.0.0/0 before it ships.


How to prevent it from happening again

Manual cleanup does not scale. Put guardrails in front of the change instead of behind it.

Block it in CI with policy as code

Use a tool like Checkov, tfsec, or OPA/Conftest to fail the pipeline when a firewall rule combines public sources with sensitive ports. A Conftest/Rego policy against a Terraform plan might look like:

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"
  port := rule.allow[_].ports[_]
  port == "4505"
  msg := sprintf("Firewall %s exposes SaltStack 4505 to the internet", [rule.name])
}

Extend the same rule for 4506 and for port ranges that contain those values.

Use GCP Org Policy constraints

Apply the compute.vmExternalIpAccess constraint to stop instances from getting external IPs unless explicitly allowed. No external IP means no public attack surface for the Salt master even if a firewall rule slips through.

Tip: Hierarchical firewall policies let you enforce a deny on 4505/4506 from public ranges at the organization or folder level. Even if a project owner writes a permissive VPC rule, the higher-priority org policy wins.

Monitor continuously

CI gates catch IaC changes, but console edits and emergency gcloud tweaks bypass them. Run Lensix on a schedule so vpc_opensalt and related network checks flag drift shortly after it appears, not months later during an incident.


Best practices

  • Never expose control planes publicly. SaltStack, plus SSH, RDP, Kubernetes API, and database ports, belong behind a VPN, bastion, or IAP, not on 0.0.0.0/0.
  • Default to deny. Start every firewall posture from "block everything" and open the minimum required paths to the minimum required sources.
  • Scope with tags or service accounts. Target the Salt rule only at instances tagged salt-master so it cannot accidentally apply to unrelated hosts.
  • Patch Salt promptly. The 2020 mass compromise hit hosts that were both exposed and unpatched. Keep your Salt version current and watch for security advisories.
  • Bind the master to a private interface. Configure the Salt master to listen on its internal address rather than all interfaces, adding a second layer of protection behind the firewall.
  • Review broad source ranges regularly. Any rule with 0.0.0.0/0 deserves a recurring look. Document why it exists or remove it.

SaltStack earns its trust by having deep control over your infrastructure. That trust is exactly why its ports must never face the open internet. Scope 4505 and 4506 to the hosts that need them, enforce it in code, and let continuous scanning catch the day someone forgets.

Fix Public SaltStack Ports on GCP Firewalls | Lensix | Lensix