Multi-Cloud · lesson 5 of 8
Intermediate
5Advanced
3Also part of Multi-Cloud
8- + CloudWatch Monitoring Basics from AWS
- + Azure Monitor Basics from Azure
- + Cloud Monitoring Basics from Google Cloud
- + AWS Cost and Well-Architected Basics from AWS
- + Azure Cost Management Basics from Azure
- + GCP Cost and Billing Basics from Google Cloud
- + Availability, Regions, and the Arithmetic of Nines from Cloud Interviews
- + Scaling, Queues, and Backpressure from Cloud Interviews
Also part of: Networking Cloud Security
Cloud Networking Basics: What's the Same Everywhere
VPCs, subnets, and firewall rules share one model everywhere — the same allow-443-deny-rest rule as a security group, an NSG rule, and a GCP rule.
By SkillByExample · Published
Restrict inbound access to port 443 only, deny everything else. The task, the default posture, and even the “stateful response traffic is automatically allowed” behavior are identical across all three clouds. Only the object you configure it on changes.
The mapping
| Concept | AWS | Azure | GCP |
|---|---|---|---|
| Isolated network | VPC (regional) | VNet (regional) | VPC network (global, multi-region) |
| Network subdivision | Subnet | Subnet | Subnet |
| Traffic filter | Security group (attached to ENI/instance) | Network Security Group (attached to subnet or NIC) | Firewall rule (attached via target tags/service account, defined on the VPC) |
| Default posture | Deny all inbound, allow all outbound | Deny all inbound below built-in AllowVnetInBound, allow outbound | Implied deny-all ingress, implied allow-all egress |
| Can write explicit deny? | No — allow-list only | Yes — deny rules with priority | Yes — deny rules with priority |
AWS: security group
aws ec2 create-security-group \
--group-name web-https-only \
--description "Allow inbound 443 only" \
--vpc-id vpc-0a1b2c3d4e5f60718
aws ec2 authorize-security-group-ingress \
--group-id sg-0f1e2d3c4b5a69788 \
--protocol tcp --port 443 --cidr 0.0.0.0/0
$ aws ec2 authorize-security-group-ingress --group-id sg-0f1e2d3c4b5a69788 --protocol tcp --port 443 --cidr 0.0.0.0/0
{
"Return": true,
"SecurityGroupRules": [
{
"SecurityGroupRuleId": "sgr-0a1b2c3d4e5f60718",
"GroupId": "sg-0f1e2d3c4b5a69788",
"IsEgress": false,
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"CidrIpv4": "0.0.0.0/0"
}
]
}
Nothing was written to “deny everything else” — there’s no deny rule in the output, and there doesn’t need to be. A newly created security group starts with zero inbound rules, which already means deny-all; adding the 443 allow rule is the entire task. This is the one place AWS’s model is simpler than the other two, precisely because it can’t express anything more complex than allow-lists.
Azure: NSG rule
az network nsg create --resource-group learn-rg --name web-https-only-nsg
az network nsg rule create \
--resource-group learn-rg --nsg-name web-https-only-nsg \
--name allow-https --priority 100 \
--direction Inbound --access Allow --protocol Tcp \
--source-address-prefixes '*' --destination-port-ranges 443
$ az network nsg rule create --resource-group learn-rg --nsg-name web-https-only-nsg --name allow-https --priority 100 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes '*' --destination-port-ranges 443
{
"access": "Allow",
"destinationPortRange": "443",
"direction": "Inbound",
"name": "allow-https",
"priority": 100,
"protocol": "Tcp",
"provisioningState": "Succeeded",
"sourceAddressPrefix": "*"
}
priority: 100 matters because NSGs evaluate rules in ascending priority order and stop at
the first match. Below every custom rule, Azure adds built-in rules at priority 65000+
(DenyAllInBound is the last one) — so “deny everything else” here is that built-in
rule, not something you write, unless you explicitly want a higher-priority deny for a
specific source ahead of a broader allow.
GCP: firewall rule
gcloud compute firewall-rules create allow-https \
--network=default \
--direction=INGRESS \
--action=ALLOW \
--rules=tcp:443 \
--source-ranges=0.0.0.0/0 \
--target-tags=web
$ gcloud compute firewall-rules create allow-https --network=default --direction=INGRESS --action=ALLOW --rules=tcp:443 --source-ranges=0.0.0.0/0 --target-tags=web
Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED
allow-https default INGRESS 1000 tcp:443 False
--target-tags=web is the part with no direct security-group equivalent: the rule applies
only to instances carrying the web network tag, not to every instance in the VPC. Every
GCP VPC also carries an implied, unlisted deny-all-ingress rule at the lowest priority
(65535) that this rule (priority 1000) takes precedence over — visible only when you list
with --format verbose enough to include implied rules.
The one real divergence
Try to write “allow 443 from anywhere except 198.51.100.0/24” as a single rule set:
- Azure: two rules — a higher-priority (lower number)
Denyfor198.51.100.0/24, then a lower-priorityAllowfor0.0.0.0/0. - GCP: same idea — a lower-priority-number
deny-blocklistrule ranked ahead of the broaderallow-httpsrule. - AWS: cannot be expressed in one security group at all. A security group has no deny rules; the only route is a separate mechanism entirely — a Network ACL at the subnet level, which does support explicit deny and is evaluated before the security group.
That’s the one place where “the model is the same everywhere” breaks down: AWS split allow-only (security group, stateful, instance-level) and allow/deny (NACL, stateless, subnet-level) into two different objects with two different evaluation semantics, where Azure and GCP each solved it with a single stateful, priority-ordered object.
Practice
1. Add a rule to each cloud's config above that also allows port 22 from a single IP, not from anywhere.
aws ec2 authorize-security-group-ingress --group-id sg-0f1e2d3c4b5a69788 --protocol tcp --port 22 --cidr 203.0.113.50/32
az network nsg rule create --resource-group learn-rg --nsg-name web-https-only-nsg --name allow-ssh-admin --priority 110 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes 203.0.113.50/32 --destination-port-ranges 22
gcloud compute firewall-rules create allow-ssh-admin --network=default --direction=INGRESS --action=ALLOW --rules=tcp:22 --source-ranges=203.0.113.50/32 --target-tags=web
All three take a /32 CIDR the same way — narrowing a source range to one address needs no
special syntax on any of them.
2. Write the AWS NACL rules that achieve "deny 198.51.100.0/24, allow everything else on 443" — the thing a security group alone cannot do.
aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 \
--rule-number 100 --protocol tcp --port-range From=443,To=443 \
--cidr-block 198.51.100.0/24 --rule-action deny --ingress
aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 \
--rule-number 200 --protocol tcp --port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 --rule-action allow --ingress
--rule-number 100 before 200 is doing the same job as Azure’s --priority 100 before
110 and GCP’s implicit priority ordering — lower number evaluated first, first match
wins.
3. List the implied/default rules on a GCP VPC that never show up in `firewall-rules list` output by default.
gcloud compute networks describe default --format="yaml(name)"
The implied deny-all-ingress and allow-all-egress rules are documented behavior, not
objects you can list directly — they exist only as the fallback when no explicit rule
matches, which is worth knowing so an empty firewall-rules list doesn’t get mistaken for
“everything is open.”
4. Confirm the stateful-return-traffic claim: allow inbound 443 only, with no outbound rule at all, and check that an HTTPS response still reaches the client.
On all three clouds, a request that matches the inbound-443 allow rule gets its response traffic passed automatically, with zero outbound rules configured — try loading the page from a browser and it works, because “stateful” means the return leg of an already-allowed connection is tracked and permitted without a matching rule of its own.
Next: RDS, Azure Database for PostgreSQL, and Cloud SQL — failover, backup mechanics, and pricing for an equivalent instance size.