23_2 ‐ OCI Hub‐and‐Spoke: Public Hub VCN Private Spoke VCN (with DRG, NGW, SGW, Bastion) - SanjeevOCI/Study GitHub Wiki
Centralize internet egress & admin in the Hub VCN, keep Spoke VCN private (no direct internet), while enabling:
- Object Storage / OSMS privately (Service Gateway)
- OS updates / public repos via Hub NAT Gateway
- Admin via Bastion in Hub
+--------------------+
| On-Prem / VPN |
| (Optional Future) |
+---------+----------+
|
v
+-------------+
| DRG |
| (Transit) |
+------+------+
|
+---------------------------+---------------------------+
| |
v v
+-------------------+ +-------------------+
| HUB VCN | | SPOKE VCN |
| 10.0.0.0/16 | | 10.1.0.0/16 |
| | | |
| +-------------+ | | +-------------+ |
| | Hub-Public |<-------- Admin (SSH/RDP) ---------| | Spoke-Priv | |
| | 10.0.1.0/24 | | | | 10.1.1.0/24 | |
| | Bastion | | | | VM (no PIP) | |
| +-------------+ | | +-------------+ |
| | | |
| +-------------+ | | |
| | Hub-Priv | | | |
| | 10.0.2.0/24 | | | |
| | NAT GW ---> IGW -----------------+ | |
| +-------------+ \ | |
| \ \ | |
| +-------------+ \ \ | |
| | SGW-HUB(opt)|----X (SGW non-transit) X | |
| +-------------+ +-------------+ |
+-------------------+ | SGW-SPOKE | |
| (ObjStor) | |
+-------------+ |
Tenancy
└─ Compartments
├─ Network-Hub
│ └─ VCN HUB 10.0.0.0/16
│ ├─ Subnet Hub-Public 10.0.1.0/24 [IGW] (Bastion, optional tools)
│ ├─ Subnet Hub-Private 10.0.2.0/24 [NAT path/NVA optional]
│ ├─ IGW, NAT GW, DRG, (SGW-HUB optional for hub workloads)
└─ App-Spoke
└─ VCN SPOKE 10.1.0.0/16
├─ Subnet Spoke-Private 10.1.1.0/24 (VMs, no public IPs)
├─ SGW-SPOKE (required for private PaaS access)
└─ DRG attachment (to Hub’s DRG)
- NAT Gateway is in the Hub (not in Spoke).
-
Spoke sends
0.0.0.0/0
to DRG (centralized egress via Hub NAT). - Each VCN that needs private PaaS must have its own Service Gateway (SGW is not transitive).
-
HUB VCN:
10.0.0.0/16
- Hub-Public:
10.0.1.0/24
- Hub-Private:
10.0.2.0/24
- Hub-Public:
-
SPOKE VCN:
10.1.0.0/16
- Spoke-Private:
10.1.1.0/24
- Spoke-Private:
-
VCN (10.0.0.0/16) with subnets:
- Hub-Public (10.0.1.0/24) for Bastion
- Hub-Private (10.0.2.0/24) for NAT path / future NVA
-
Gateways in Hub:
- IGW (Internet Gateway)
- NAT Gateway
- DRG
- (Optional) SGW-HUB if hub workloads need private PaaS
-
Route Tables (Hub):
-
Hub-Public RT:
0.0.0.0/0 → IGW
-
Hub-Private RT:
0.0.0.0/0 → NAT GW
-
(optional)
all-oci-services-in-oracle-services-network → SGW-HUB
-
Hub-Public RT:
- Place OCI Bastion in Hub-Public subnet (uses IGW).
- Use Bastion session to reach Spoke VM (no public IP on Spoke VM).
- VCN (10.1.0.0/16) with Spoke-Private (10.1.1.0/24)
- SGW-SPOKE (Service Gateway) — required for Object Storage/OSMS
- Attach Spoke VCN to Hub’s DRG
- Create DRG attachments: Hub VCN & Spoke VCN
-
DRG Route Tables & Distributions (simple, centralized egress):
-
Hub → Spoke: Export default route
0.0.0.0/0
to Spoke -
Spoke → Hub: Export Spoke CIDR
10.1.0.0/16
to Hub - (Optional) Add on-prem later by attaching VPN/FC to the same DRG
-
Hub → Spoke: Export default route
-
all-oci-services-in-oracle-services-network → SGW-SPOKE
(private PaaS) -
0.0.0.0/0 → DRG
(centralized egress via Hub NAT)
Spoke (subnet or NSG at VNIC):
-
Egress:
-
all-oci-services-in-oracle-services-network
, TCP 443 (SGW) -
0.0.0.0/0
, TCP 443 (NAT path via DRG → Hub)
-
-
Ingress:
- From Hub Bastion/VPN CIDR on admin ports (22/3389 as required)
- No inbound from internet
Hub (Bastion + NAT path):
- Bastion subnet: allow inbound from trusted admin IPs (SSH session establishment)
- If using NVA (optional): allow spoke CIDR → NVA, and NVA → IGW as required
- Create VM in Spoke-Private, no public IP
- Connect via Bastion (Hub-Public)
Object Storage / OSMS via SGW (private path):
oci os ns get --region <region>
oci os bucket list --compartment-id <compartment_ocid> --region <region>
# Oracle Linux repos via OSMS (private):
sudo dnf check-update # or: sudo yum check-update
Outbound updates via Hub NAT (public repos):
sudo dnf install -y wget # or: sudo yum install -y wget
No broad internet (if you limited egress to 443):
curl http://example.com:80 # should fail (NSG egress locked to 443)
Path sanity:
-
Spoke VNIC → Effective Routes show:
all-oci-services... → SGW-SPOKE
0.0.0.0/0 → DRG
- Hub NAT subnet RT → 0.0.0.0/0 → IGW
- NAT GW = outbound-only for private workloads; no unsolicited inbound possible.
- Using IGW for Spoke traffic opens inbound paths unless heavily firewalled (breaks “no-internet Spoke” posture).
-
Best practice:
- Public subnets → IGW
- Private subnets → NAT GW
“We centralize egress by moving NAT to the Hub and point the Spoke’s
0.0.0.0/0
to the DRG.
This preserves the Spoke’s no-internet posture while enabling updates, and aligns with OCI’s gateway semantics.”
- Trying to use Hub SGW for Spoke: Not supported (SGW is not transitive). Create SGW in each VCN that needs private PaaS.
- Missing DRG export/import: Spoke won’t see default route. Fix DRG RT + distributions.
- Asymmetric routing with an NVA: Ensure return routes in Hub point back to DRG for Spoke CIDR and to IGW for internet.
- Wrong route table attached to Spoke subnet: Double-check association.
- Security rules too open: Keep Spoke egress TCP/443 only; prefer NSGs for per-VM control.
- Gateways are VCN-level (NAT/SGW): create gateway → reference in subnet RT.
- DRG provides transit + policy (DRG RTs & distributions) across many VCNs and on-prem.
- SGW is non-transitive: every VCN needs its own SGW.
- Bastion in Hub Public for admin; Spoke VMs never get public IPs.
- Effective routes & flow logs: verify NIC routes and use VCN flow logs.
- Egress least privilege: 443 only; switch to NVA/OCI Network Firewall for FQDN/domain allow-listing.
- Hub: IGW, NAT GW, DRG (+ optional SGW-HUB), Bastion in Hub-Public
- Spoke: SGW-SPOKE, DRG attachment; no IGW
-
DRG: Export
0.0.0.0/0
(Hub → Spoke), import Spoke CIDR (Spoke → Hub) -
Routes (Spoke):
all-oci-services… → SGW-SPOKE
,0.0.0.0/0 → DRG
- Routes (Hub): Hub-Public → IGW, Hub-Private → NAT GW (+ returns to Spoke CIDR)
- Security: Spoke egress TCP/443 to SGW + 0.0.0.0/0; admin from Bastion/VPN only
-
Validate:
oci os ns get curl ifconfig.me dnf/yum check-update