Description
This rule verifies that no DigitalOcean Cloud Firewall contains an inbound rule that permits SSH (TCP port 22) from an unrestricted source. On DigitalOcean, Cloud Firewalls are the equivalent of the security-group construct found on other clouds; there is no separate 'security group' object. An unrestricted source is expressed in the API and CLI as the address 0.0.0.0/0 (all IPv4) or ::/0 (all IPv6), which the Control Panel displays as 'All IPv4' and 'All IPv6'.
The rule enumerates every firewall's inbound_rules and flags any rule whose protocol is TCP, whose port range includes 22, and whose sources include 0.0.0.0/0 or ::/0. Because a Droplet can be attached to more than one firewall and rules combine as a permissive union, the rule inspects all firewalls, not only the one nearest a given Droplet.
Remediation narrows the source of the port-22 rule to specific administrative IPs, a VPN egress address, or a bastion/jump host, and removes the unrestricted rule once the scoped replacement is confirmed to work.
Inbound TCP rules on port 22 with source 0.0.0.0/0
Inbound TCP rules on port 22 with source ::/0
Port-range rules (e.g. 0-1024 or 'all') that encompass port 22 from an unrestricted source
Rationale
SSH on port 22 is one of the most heavily targeted services on the public internet. Internet-wide scanners continuously probe for open port 22 and launch automated brute-force and credential-stuffing attacks against any host that answers. Exposing SSH to 0.0.0.0/0 removes the network as a line of defense and leaves only the SSH daemon's authentication to stand between an attacker and a shell on the Droplet.
Restricting the source to a small set of known administrative addresses shrinks the attack surface dramatically: an attacker must first be on an allowed network before the SSH service is even reachable, which stops the overwhelming majority of opportunistic attacks at the network layer.
Blocks opportunistic internet-wide SSH brute-force before it reaches sshd
Reduces log noise and false-positive alerting from constant scan traffic
Aligns with defense-in-depth: network filtering plus key-based auth
Impact
Pros:
Eliminates a primary automated attack vector against Droplets
Confines administrative access to trusted networks
Cuts down authentication-failure log volume and alert fatigue
Cons:
Administrators on dynamic or roaming IPs must maintain the allow-list or connect through a VPN/bastion
An overly tight allow-list can lock out legitimate operators during an incident if not planned
Requires knowing and tracking the source IPs your team connects from
Default Value
When you create a firewall in the Control Panel the suggested inbound rule allows SSH on port 22 from anywhere (All IPv4 and All IPv6); firewalls otherwise deny all inbound traffic that no rule permits.
Pre-Requisite
DigitalOcean Control Panel access with permission to view and edit firewalls (owner or member role on the team)
A DigitalOcean API token, or doctl authenticated via 'doctl auth init', for API/CLI verification
Knowledge of the administrative source IPs, VPN egress addresses, or bastion host used for SSH
Remediation
Test Plan
Using the DigitalOcean Control Panel:
Sign in to the Control Panel at cloud.digitalocean.com.
Go to Networking, then Firewalls.
Open each firewall and select the Rules tab.
Under Inbound Rules, look for any rule of type SSH or a Custom TCP rule whose Ports include 22.
Inspect the Sources column for that rule; note whether it lists 'All IPv4' and/or 'All IPv6'.
Pass: No inbound rule allowing TCP port 22 has a source of All IPv4 (0.0.0.0/0) or All IPv6 (::/0); every port-22 rule is scoped to specific addresses or resources.
Fail: At least one inbound rule allows TCP port 22 from All IPv4 or All IPv6.
Using doctl or the API:
Enumerate firewalls and inspect inbound rules for port 22 open to an unrestricted source.
doctl compute firewall list --format ID,Name,InboundRules
# Or via the API, then inspect inbound_rules for protocol tcp, ports "22", sources.addresses containing 0.0.0.0/0 or ::/0:
curl -s -X GET -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/firewalls" | jq '.firewalls[] | {id, name, offending: [.inbound_rules[] | select(.protocol=="tcp" and (.ports=="22" or .ports=="0") and (.sources.addresses // [] | any(.=="0.0.0.0/0" or .=="::/0")))]}'
Pass: the offending array is empty for every firewall. Fail: any firewall returns an inbound TCP rule covering port 22 with a source address of 0.0.0.0/0 or ::/0.
Implementation Steps
Using the DigitalOcean Control Panel:
In Networking, then Firewalls, open the firewall and select the Rules tab.
Click Add inbound rule, choose SSH (or Custom TCP with port 22), and in the Sources field enter your specific administrative IPs, CIDR ranges, a VPN egress address, or a bastion Droplet/tag.
Save the new scoped rule and confirm you can still reach the Droplet over SSH from an allowed source.
Locate the original port-22 rule whose source is All IPv4 / All IPv6, click the … (More) menu beside it, and choose Delete rule.
Repeat for every firewall that exposes SSH to an unrestricted source.
Using doctl or the API:
Add a scoped port-22 rule, then remove the unrestricted one.
doctl compute firewall add-rules <firewall-id> --inbound-rules "protocol:tcp,ports:22,address:203.0.113.10/32"
doctl compute firewall remove-rules <firewall-id> --inbound-rules "protocol:tcp,ports:22,address:0.0.0.0/0"
# Equivalent API calls:
curl -s -X POST -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" -d '{"inbound_rules":[{"protocol":"tcp","ports":"22","sources":{"addresses":["203.0.113.10/32"]}}]}' "https://api.digitalocean.com/v2/firewalls/<firewall-id>/rules"
curl -s -X DELETE -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" -d '{"inbound_rules":[{"protocol":"tcp","ports":"22","sources":{"addresses":["0.0.0.0/0","::/0"]}}]}' "https://api.digitalocean.com/v2/firewalls/<firewall-id>/rules"
Backout Plan:
If the scoped rule blocks legitimate access, re-add the previous rule with doctl compute firewall add-rules <firewall-id> --inbound-rules "protocol:tcp,ports:22,address:0.0.0.0/0" (or via POST /v2/firewalls/<firewall-id>/rules) to temporarily restore the prior state.
Once corrected source addresses are confirmed, re-apply the scoped rule and remove the unrestricted rule again.