GCP for DevOps — Firewall Rules

3 minute read

Firewall Rules

GCP firewall rules control inbound (ingress) and outbound (egress) network traffic to and from VM instances. They are stateful, priority-based, and can be applied using network tags or service accounts.

How Firewall Rules Work

                   Internet
                      │
              ┌───────▼───────┐
              │ GCP Firewall   │
              │                │
              │ Rule 1: ALLOW  │ ◄── Priority 1000
              │   tcp:80,443   │     (lower = higher priority)
              │   tag:web      │
              │                │
              │ Rule 2: DENY   │ ◄── Priority 65534
              │   all traffic  │     (implied deny-all ingress)
              │                │
              └───────┬───────┘
                      │
              ┌───────▼───────┐
              │  VM Instance   │
              │  tag: web      │
              └───────────────┘

Default Firewall Rules

Every VPC network starts with two implied rules:

Rule Direction Priority Action Description
Implied allow egress Egress 65535 Allow All outbound traffic allowed
Implied deny ingress Ingress 65535 Deny All inbound traffic denied

Plus default VPC adds:

Rule Ports Source Priority
default-allow-internal All 10.128.0.0/9 65534
default-allow-ssh TCP 22 0.0.0.0/0 65534
default-allow-rdp TCP 3389 0.0.0.0/0 65534
default-allow-icmp ICMP 0.0.0.0/0 65534

⚠️ DevOps Best Practice: Delete the default allow-ssh and allow-rdp rules. Use IAP for SSH/RDP access instead of exposing ports to the internet.

Creating Firewall Rules

# Allow HTTP and HTTPS from the internet to VMs with tag "web"
$ gcloud compute firewall-rules create allow-http-https \
    --network=default \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:80,tcp:443 \
    --source-ranges=0.0.0.0/0 \
    --target-tags=web \
    --priority=1000 \
    --description="Allow HTTP/HTTPS to web servers"

Creating firewall...done.
NAME              NETWORK  DIRECTION  PRIORITY  ALLOW          DENY  DISABLED
allow-http-https  default  INGRESS    1000      tcp:80,tcp:443              False

# Allow SSH only from IAP's IP range
$ gcloud compute firewall-rules create allow-ssh-iap \
    --network=default \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:22 \
    --source-ranges=35.235.240.0/20 \
    --target-tags=allow-ssh \
    --priority=1000 \
    --description="Allow SSH from IAP only"

# Allow internal communication between app servers
$ gcloud compute firewall-rules create allow-internal-app \
    --network=default \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:8080,tcp:8443 \
    --source-tags=app \
    --target-tags=app \
    --priority=1000

Using Service Accounts Instead of Tags

Service account-based rules are more secure because tags can be set by anyone with compute.instances.setTags permission.

# Allow traffic to VMs running with a specific service account
$ gcloud compute firewall-rules create allow-app-to-db \
    --network=default \
    --direction=INGRESS \
    --action=ALLOW \
    --rules=tcp:5432 \
    --source-service-accounts=app-sa@my-project.iam.gserviceaccount.com \
    --target-service-accounts=db-sa@my-project.iam.gserviceaccount.com \
    --priority=1000 \
    --description="Allow app tier to connect to database on port 5432"

Deny Rules

# Deny all egress to a specific IP range
$ gcloud compute firewall-rules create deny-egress-malicious \
    --network=default \
    --direction=EGRESS \
    --action=DENY \
    --rules=all \
    --destination-ranges=198.51.100.0/24 \
    --priority=900

# Deny SSH from the internet (override default rule)
$ gcloud compute firewall-rules create deny-ssh-internet \
    --network=default \
    --direction=INGRESS \
    --action=DENY \
    --rules=tcp:22 \
    --source-ranges=0.0.0.0/0 \
    --priority=900

Priority System

Lower numbers = higher priority. Range: 0–65535.

Priority 0     ←── Highest (evaluated first)
Priority 100
Priority 1000  ←── Typical custom rules
Priority 65534 ←── Default rules
Priority 65535 ←── Implied rules (cannot be deleted)
# List firewall rules sorted by priority
$ gcloud compute firewall-rules list \
    --format="table(name,direction,priority,allowed[].map().firewall_rule().list():label=ALLOWED,sourceRanges.list():label=SRC_RANGES,targetTags.list():label=TARGET_TAGS)" \
    --sort-by=priority

NAME                    DIRECTION  PRIORITY  ALLOWED          SRC_RANGES       TARGET_TAGS
deny-ssh-internet       INGRESS    900       deny:tcp:22      0.0.0.0/0
allow-http-https        INGRESS    1000      tcp:80,tcp:443   0.0.0.0/0        web
allow-ssh-iap           INGRESS    1000      tcp:22           35.235.240.0/20  allow-ssh
default-allow-internal  INGRESS    65534     all              10.128.0.0/9

Firewall Rule Logging

Enable logging to track which rules are being hit.

# Enable logging on a firewall rule
$ gcloud compute firewall-rules update allow-http-https \
    --enable-logging

# View firewall logs
$ gcloud logging read 'resource.type="gce_subnetwork" AND jsonPayload.disposition="ALLOWED"' \
    --project=my-project --limit=5

Common Mistakes

Mistake Why It’s Wrong Fix
Allowing SSH from 0.0.0.0/0 Exposes all VMs to brute-force Use IAP (35.235.240.0/20)
Not setting priorities Rules may conflict unexpectedly Always set explicit priorities
Using only tags (not SA) Tags can be manipulated Use service account-based rules
No egress rules VMs can reach anything Add deny-egress for sensitive networks
Not enabling logging Can’t troubleshoot connectivity Enable logging on important rules

Quick Reference

Command Description
gcloud compute firewall-rules create NAME --rules=PROTO:PORT Create a rule
gcloud compute firewall-rules list List all rules
gcloud compute firewall-rules describe NAME Show rule details
gcloud compute firewall-rules update NAME Update a rule
gcloud compute firewall-rules delete NAME Delete a rule
--source-ranges=CIDR Filter by source IP
--target-tags=TAG Apply to VMs with tag
--source-service-accounts=SA Filter by source SA

Next → Startup Scripts

Updated: