Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Networking & Firewalls: AWS vs Azure vs GCP

Coming from AWS/Azure. Focus: the firewall / security-group model, plus the structural VPC differences that change how everything else feels.

1. Structural model (learn this first)

ConceptAWSAzureGCP
VPC scopeRegionalRegional (VNet)Global — one VPC spans every region
Subnet scopeZonal (1 AZ)RegionalRegional (spans zones in the region)
RoutingRoute table per subnetRoute table (UDR) per subnetRoutes are VPC-global, next-hop by instance tag

Key shift: a GCP VPC is global. A single VPC reaches europe-west1 and us-central1 over internal IPs with no peering (in AWS you'd need Transit Gateway / VPC peering). Subnets stay regional (like Azure).

Because subnets auto-create in every region by default, real setups use auto_create_subnetworks = false and define subnets explicitly.

2. Firewall model — the core difference

AWS splits firewalling into two layers; GCP collapses it into one.

CloudLayered constructs
AWSSecurity Groups (stateful, instance-level, allow-only) + NACLs (stateless, subnet-level, allow+deny, ordered)
AzureNSGs (stateful, allow+deny, priority) + ASGs (logical NIC groups)
OCISecurity Lists (subnet-level) + NSGs (VNIC-level)
GCPVPC Firewall Rules — one construct doing the job of both AWS layers

The defining GCP trait

Firewall rules are NOT attached to instances. They live on the VPC network, and you pick which instances they hit via targets:

  • target_tags — network tags on instances
  • target_service_accounts — the instance's identity
  • or all instances in the network

Source (for ingress) can equally be IP ranges, tags, or service accounts.

Property comparison

PropertyGCP firewall ruleClosest AWSClosest Azure
Stateful?Yes (return traffic auto-allowed)SG yes / NACL noNSG yes
Allow and deny?YesNACL only (SG allow-only)NSG yes
Priority?Yes, 0–65535, lower wins, default 1000NACL rule #s; SG noneNSG 100–4096, lower wins
Applied toInstances via tag / service accountENI (SG) / subnet (NACL)NIC and/or subnet
DirectionIngress / egressBothBoth

So: as stateful as an AWS SG, but with deny + priority like a NACL/NSG, all in one object, targeted by tag or identity instead of by attachment.

Implied / default rules (all clouds: deny-inbound by default)

Every GCP VPC has two implied rules at priority 65535:

  • Implied deny ingress
  • Implied allow egress

Same posture as AWS SG and Azure NSG defaults. The GCP default network also ships default-allow-ssh/-rdp/-icmp/-internal — a reason to build a custom VPC so nothing is pre-opened.

3. Two GCP superpowers with no clean AWS/Azure twin

1) Identity-based firewalling via service accounts. A rule's source/target can be a service account, not an IP or tag:

"Allow 5432 to instances running as db-sa@…, only from instances running as app-sa@…."

Microsegmentation by workload identity. AWS approximates with SG-references-SG; Azure with ASGs; neither ties to a real IAM identity like GCP does.

⚠️ Network tags are not a security boundary — anyone with compute.instances.setTags can add a tag and inherit its access. Service-account rules are the hardened choice (changing an instance's SA needs a stop + stronger IAM).

2) Hierarchical Firewall Policies. Rules attached at org / folder level, evaluated before VPC rules — org-wide guardrails. Analogous to AWS Firewall Manager / Org SCP or Azure Firewall Manager / Policy, but native to the firewall. Newer Network Firewall Policies add address groups, FQDN, geo/threat-intel (≈ Azure Firewall L7).

4. Cheat sheet

You want to…AWSAzureGCP
Stateful instance firewallSecurity GroupNSG (on NIC)Firewall rule w/ target tag/SA
Stateless subnet ACL w/ denyNACLNSG (on subnet)Firewall rule w/ priority + deny
Group workloads as a rule sourceSG-references-SGASGTarget/source service account
Org-wide network guardrailsFirewall ManagerFirewall Manager / PolicyHierarchical firewall policy
Private admin access (no bastion)SSM Session ManagerAzure BastionIAP TCP forwarding
Managed L7 firewallNetwork FirewallAzure FirewallNetwork Firewall Policy / Cloud NGFW

5. Example rule (Terraform)

resource "google_compute_firewall" "allow_pg" {
  network       = google_compute_network.vpc.id   # rule lives on the NETWORK
  source_ranges = ["34.x.x.x/29"]                 # ingress source = IP CIDRs
  target_tags   = ["pg"]                          # applies to tagged instances
  allow { protocol = "tcp"; ports = ["5432"] }    # stateful; default priority 1000
}

AWS eyes: behaves like a Security Group allowing 5432, but instead of attaching to the instance it targets the pg tag — and it could have been a deny, which an AWS SG can't express.

SSH without a public bastion: open only Google's IAP range 35.235.240.0/20 (≈ AWS SSM Session Manager / Azure Bastion).

6. "Unlearn from AWS" takeaways

  1. Stop thinking "attach SG to instance" → write a network rule, target it by tag or identity.
  2. No separate NACL layer needed — deny + priority already live in the same firewall-rules system.
  3. Firewall by service-account identity, not IP, wherever possible — spoofing-resistant, unlike tags.

7. Q&A

Q1. What do "stateful" and "stateless" mean? (with example)

Stateful = the firewall keeps a connection-tracking table. Once it allows one direction of a connection, the return traffic is allowed automatically — you never write a rule for the reply.

Stateless = every packet is judged on its own, no memory of the connection. You must explicitly allow both directions (request and reply).

Example — a client hitting a web server on 443

A client at 10.0.0.5:54321 (a random ephemeral source port) connects to a web server at 10.0.1.9:443.

Stateful (GCP firewall rule, AWS SG, Azure NSG)Stateless (AWS NACL)
Rule you writeALLOW ingress tcp/443ALLOW ingress tcp/443 and ALLOW egress tcp 1024–65535
The reply (443 → 54321)Auto-allowed (connection is tracked)Dropped unless you also add the egress rule for the ephemeral range
If you forget the reply ruleStill worksBroken — request arrives, response silently dropped

The classic stateless footgun: you open inbound 443, it "half works," and you're left debugging why responses vanish — because you never opened the outbound ephemeral port range for the replies.

Takeaway: GCP firewall rules are stateful, so you almost never think about return traffic. The only stateless model among the big clouds is the AWS NACL — which is exactly why NACLs feel fiddly.

Q2. How is a firewall "attached" to a service account? (scenario + AWS equivalent)

In GCP, an instance runs as a service account (an IAM identity bound to the VM). A firewall rule can then match on that identity via source_service_accounts / target_service_accounts instead of IPs or tags. The rule applies to whichever instances run as that SA — no IP bookkeeping.

Scenario — app tier may reach the DB, nothing else may

  • App VMs run as app-sa@proj.iam.gserviceaccount.com
  • DB VMs run as db-sa@proj.iam.gserviceaccount.com
  • Goal: only app-tier VMs can reach Postgres (5432) on the DB tier.
# The instances declare their identity:
resource "google_compute_instance" "app" {
  # ...
  service_account { email = google_service_account.app.email }
}
resource "google_compute_instance" "db" {
  # ...
  service_account { email = google_service_account.db.email }
}

# The rule matches on identity, not IP or tag:
resource "google_compute_firewall" "db_from_app" {
  name      = "allow-app-to-db"
  network   = google_compute_network.vpc.id
  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["5432"]
  }

  target_service_accounts = [google_service_account.db.email]  # applies to DB VMs
  source_service_accounts = [google_service_account.app.email] # allowed callers
}

Why it's nice: an autoscaler can spin up 50 new app VMs with brand-new IPs — they're allowed the instant they boot, because they run as app-sa. No IP lists, no tag to spoof. Changing a VM's SA requires **stopping it + iam.serviceAccountUser

  • compute permissions**, so access is gated by IAM, not by a mutable label.

⚠️ Restriction: you can't mix service accounts and tags in the same rule (no source_service_accounts together with source_tags/target_tags). Pick one model per rule.

AWS equivalent — Security Group referencing another Security Group

AWS has no IAM-identity firewalling, but the same pattern is expressed with one SG referencing another as its source. Membership in the app SG grants access, much like membership-by-service-account in GCP.

resource "aws_security_group" "app" { name = "app-sg" vpc_id = aws_vpc.main.id }
resource "aws_security_group" "db"  { name = "db-sg"  vpc_id = aws_vpc.main.id }

# DB SG allows 5432 only from instances in the app SG:
resource "aws_security_group_rule" "db_from_app" {
  type                     = "ingress"
  from_port                = 5432
  to_port                  = 5432
  protocol                 = "tcp"
  security_group_id        = aws_security_group.db.id   # attached to DB instances
  source_security_group_id = aws_security_group.app.id  # membership = access
}
GCPAWSAzure
Access is granted by…IAM service account the VM runs asSG membership on the ENIASG membership on the NIC
Rule constructsource/target_service_accountssource_security_group_idNSG rule with source = ASG
Gated byIAM permissions (change SA)EC2 permissions (change SG attach)Network permissions (change ASG)

Difference that matters: GCP binds to a real IAM identity (spoofing- resistant, auditable in IAM); AWS/Azure bind to network-group membership on the interface. Same "membership grants access" idea, different trust anchor.