Security Lists and Network Security Groups handle stateful packet filtering at the subnet and VNIC level. They are the right tool for controlling which ports and protocols reach a resource. What they cannot do is inspect the content of traffic, detect threats based on application-layer signatures, block specific URLs or FQDNs, or apply SSL inspection to decrypt and re-encrypt traffic in flight. That requires a different layer entirely.
OCI Network Firewall is Oracle’s managed next-generation firewall service, built on Palo Alto Networks technology and integrated natively into VCN routing. It supports application-layer inspection, IDPS (Intrusion Detection and Prevention), URL filtering, FQDN-based rules, and TLS inspection. Unlike a third-party firewall appliance you would deploy on a compute instance, OCI Network Firewall is a fully managed service: Oracle handles the underlying infrastructure, HA, and scaling. You manage the policy.
In this post I will walk through designing a hub-and-spoke inspection architecture, deploying the firewall and its policy using Terraform, configuring IDPS and URL filtering rules, and validating traffic flow with OCI Flow Logs.
Architecture: Hub-and-Spoke with Centralized Inspection
The standard pattern for OCI Network Firewall in multi-VCN environments is centralized inspection through a hub VCN. All spoke VCNs route traffic through the hub, and the firewall sits in the hub inspecting both north-south (internet-bound) and east-west (spoke-to-spoke) traffic.
Traffic routing in this architecture uses a combination of DRG route tables and VCN ingress/egress route tables to steer all flows through the firewall subnet before they reach their destination. This is the most important concept to get right: the firewall only inspects traffic that is routed through it. Misconfigured route tables mean packets bypass the firewall entirely with no error or warning.
Step 1: Hub VCN and Firewall Subnet
resource "oci_core_vcn" "hub_vcn" { compartment_id = var.compartment_id cidr_blocks = ["192.168.0.0/16"] display_name = "hub-inspection-vcn" dns_label = "hubvcn"}# Firewall subnet - the firewall VNIC lives hereresource "oci_core_subnet" "firewall_subnet" { compartment_id = var.compartment_id vcn_id = oci_core_vcn.hub_vcn.id cidr_block = "192.168.1.0/24" display_name = "firewall-subnet" dns_label = "fwsubnet" prohibit_public_ip_on_vnic = true route_table_id = oci_core_route_table.firewall_subnet_rt.id security_list_ids = [oci_core_security_list.firewall_sl.id]}# Internet Gateway for north-south trafficresource "oci_core_internet_gateway" "hub_igw" { compartment_id = var.compartment_id vcn_id = oci_core_vcn.hub_vcn.id display_name = "hub-internet-gateway" enabled = true}# DRG for spoke VCN attachmentresource "oci_core_drg" "hub_drg" { compartment_id = var.compartment_id display_name = "hub-drg"}resource "oci_core_drg_attachment" "hub_vcn_attachment" { drg_id = oci_core_drg.hub_drg.id display_name = "hub-vcn-attachment" network_details { id = oci_core_vcn.hub_vcn.id type = "VCN" }}
The firewall subnet must not have a public IP on its VNIC. The firewall receives traffic through routing, not through a public endpoint.
Step 2: Firewall Policy
The policy is the heart of the firewall. It contains address lists, URL lists, application lists, and the ordered set of security rules. All of these are defined as child resources of the policy and are applied when the policy is attached to a firewall instance.
resource "oci_network_firewall_network_firewall_policy" "production_policy" { compartment_id = var.compartment_id display_name = "production-inspection-policy"}# IP address list for trusted internal RFC1918 rangesresource "oci_network_firewall_network_firewall_policy_address_list" "internal_ranges" { name = "internal-rfc1918" network_firewall_policy_id = oci_network_firewall_network_firewall_policy.production_policy.id type = "IP" addresses = [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ]}# FQDN list for allowed outbound SaaS destinationsresource "oci_network_firewall_network_firewall_policy_address_list" "allowed_saas" { name = "allowed-saas-fqdns" network_firewall_policy_id = oci_network_firewall_network_firewall_policy.production_policy.id type = "FQDN" addresses = [ "*.oracle.com", "*.oraclecloud.com", "*.github.com", "registry-1.docker.io", "auth.docker.io", "production.cloudflare.docker.com" ]}# URL list for blocked categoriesresource "oci_network_firewall_network_firewall_policy_url_list" "blocked_urls" { name = "blocked-url-categories" network_firewall_policy_id = oci_network_firewall_network_firewall_policy.production_policy.id urls { pattern = "*.pastebin.com" type = "SIMPLE" } urls { pattern = "*.ngrok.io" type = "SIMPLE" } urls { pattern = "*.ngrok-free.app" type = "SIMPLE" }}# Application list scoping HTTPS trafficresource "oci_network_firewall_network_firewall_policy_application_group" "web_apps" { name = "web-traffic" network_firewall_policy_id = oci_network_firewall_network_firewall_policy.production_policy.id apps = ["HTTP", "HTTPS", "SSL"]}
Step 3: Security Rules
Rules are evaluated in order. The first matching rule wins. Structure your rules from most specific to most general and always end with an explicit deny-all for traffic that does not match any allow rule.