OCI Network Hardening

Overview

This page covers Oracle Cloud Infrastructure network hardening across the surfaces that decide whether an attacker reaching the network edge can pivot inward, exfiltrate data, or sustain disruption. Scope is the commercial OCI realms (OC1); OCI Government Cloud and dedicated-region tenancies inherit the same controls but expose realm-specific endpoints, region availability, and Identity Domain federation constraints — re-verify region availability and the relevant docs.oracle.com realm-endpoint documentation before applying any of the IaC below to a sovereign or dedicated-region deployment. CIS sub-IDs and NIST / ISO mappings throughout this page reference the CIS Oracle Cloud Infrastructure Foundations Benchmark v2.0.0 (accessed 2026-05) unless explicitly annotated as a post-v2.0.0 feature or a best-practice recommendation that the v2.0.0 benchmark has not yet codified. CIS published the Oracle Cloud Infrastructure Foundations Benchmark v3.1.0 in 2026; this site cites v2.0.0 throughout the corpus for consistency with the locked compliance-table contract. The crosswalk page at compliance frameworks describes how the seven pinned framework columns relate to each other.

The OCI network model is the product of a tenancy (the root identity boundary, where the realm and root compartment live), compartments (hierarchical containers that own policy, quota, and resources — owned canonically by oci-iam-07-compartment-hierarchy on the OCI IAM page; this page cross-references and does not re-author the compartment model), Virtual Cloud Networks (VCNs) (regional networks with custom CIDR allocation; no auto-mode subnets), subnets (regional or AD-specific CIDR slices, with the boolean prohibit_public_ip_on_vnic that turns a subnet into a private subnet at the data plane), VNICs (the per-host network interface that NSGs attach to), Security Lists (stateful L4 firewalls scoped to a subnet — the legacy surface that ships with every VCN), Network Security Groups (stateful L4 firewalls scoped to a VNIC, with the unique ability to reference other NSGs as source or destination — the security-group-style surface), Dynamic Routing Gateway (DRG) (the hub-and-spoke routing primitive that lets one DRG attach to many VCNs and to FastConnect / IPsec for on-prem peering), Service Gateway (the regional egress to the Oracle Services Network for Object Storage, Autonomous Database, and OCI control planes — no NAT, no Internet Gateway), Private Endpoint (the per-service-instance private VNIC inside your subnet that fronts an OCI managed service at a private IP), NAT Gateway (stateful SNAT for outbound public traffic from private subnets), Bastion service (per-session time-limited SSH or managed port-forwarding sessions to instances that have no public IP), and edge primitives Flexible Load Balancer, OCI DNS, Web Application Firewall (WAF), and Network Firewall (managed Palo Alto VM-Series for L7 / FQDN egress filtering). The cross-cutting principles — segmentation, zero trust, egress control, private connectivity, encryption in transit, and DNS security — are owned by the General Network page; this page maps them to OCI primitives. Severity is assigned from the methodology severity rubric; equivalence callouts at the bottom of each control point at the matching control on the AWS, Azure, and GCP sibling pages.

Three anti-conflation callouts up front, because each pair gets conflated in audit reports and architecture reviews and the distinction matters for control design. First: Security Lists and Network Security Groups are complementary, not alternative. Security Lists are subnet-scoped stateful firewalls — every VNIC in the subnet inherits the Security List rule set, evaluation happens per packet, and Security Lists are the legacy surface that ships with every VCN by default. Network Security Groups (oci-net-02) are VNIC-scoped stateful firewalls, with the unique ability for a rule to reference another NSG as source or destination — this is the security-group-style surface familiar from AWS, and it lets you express "tier-A may reach tier-B" without enumerating CIDRs. Both surfaces are evaluated together: a packet must satisfy the union of any matching Security List rules and any matching NSG rules. Use NSGs for VNIC-grouping logic (per-tier, per-role); use Security Lists for blanket subnet-wide defaults. Both must deny 0.0.0.0/0 ingress on admin ports; the same invariant has to hold on both surfaces because the union evaluation does not give one surface veto over the other.

Second: the OCI Bastion service is the default access path; SSH-to-public-IP is an anti-pattern, not an alternative. The Bastion service (referenced from oci-net-02 and authored canonically on the OCI Workloads page) provides per-session time-limited SSH and managed port-forwarding sessions to target instances that have no public IP; sessions are audited in the OCI Audit service, scoped to a single target, and expire after a configured TTL (default 3 hours, maximum 3 hours; renewal is explicit). Putting public IPs on workload instances and opening TCP 22 to 0.0.0.0/0 on a Security List or NSG is the canonical OCI ops anti-pattern; it is enumerated only to be ruled out. The Bastion is the default; SSH-to-public-IP is not a tradeoff to weigh.

Third: Service Gateway and Private Endpoint are complementary, not alternative. Service Gateway (oci-net-04) routes private traffic from a VCN to the Oracle Services Network — Object Storage, Autonomous Database, OKE control plane, and other OCI-managed services — over Oracle's backbone, with no NAT and no public IP on the workload; it is a regional bulk-routing primitive bound to the route table via a network_entity_id entry for all-<region>-services-in-oracle-services-network. Private Endpoint (oci-net-03) creates an explicit private VNIC inside your subnet that exposes one specific OCI managed-service instance (for example, one Autonomous Database) as a private IP you control; the consuming workload connects to a 10.x address. Service Gateway is bulk and regional; Private Endpoint is per-service-instance. PE is the right pattern when you want CIDR-level reachability of a specific managed-service instance; SG is the right pattern for the broad "talk to Object Storage and ADB control planes from this VCN without an Internet Gateway" case. Most production VCNs need both.

Order and scope matter. Controls 01–04 are foundational invariants: design the VCN explicitly (no default reuse), close 0.0.0.0/0 ingress to admin ports on both Security Lists and NSGs, front OCI managed-service instances with Private Endpoints inside private subnets, and route bulk OCI-service traffic via the Service Gateway. Control 05 is the L7 WAF on public Load Balancers. Control 06 is the platform-default L3/L4 DDoS layer and its operator-visible L7 rate-limiting complement. Control 07 signs the organisation's public DNS zones and points private resolution at private DNS views. Control 08 closes the egress loop with NAT Gateway, egress NSG and Security List rules, and Network Firewall for FQDN- or L7-aware egress filtering. The compartment hierarchy and least-privilege policy primitives are owned by the OCI IAM page and cross-referenced from this page where relevant; do not re-author them here.

oci-net-01-vcn-design ! MEDIUM PREVENTIVE

Design every workload VCN as an explicit custom-CIDR Virtual Cloud Network owned by a per-environment compartment, with subnets stratified into public (load balancers and edge only), private (workloads with no public IPs — prohibit_public_ip_on_vnic = true), and data-tier (database subnets, no Internet Gateway in the route table). Hub-and-spoke topology is expressed via a Dynamic Routing Gateway (DRG) — one DRG per region attaches to many workload VCNs and to FastConnect or IPsec circuits for on-prem peering, so transitive routing is explicit and policy-controlled rather than a side effect of VCN peering (Oracle Cloud Infrastructure — Dynamic Routing Gateway documentation (accessed 2026-05)). The principle is reinforced in General Network — segmentation: a network the tenancy did not consciously design is a network whose blast radius the tenancy cannot reason about. OCI does not ship a "default" VCN in the AWS sense, but the equivalent failure mode is workload teams creating shared-purpose VCNs in the root compartment with overlapping CIDRs that later cannot be peered without renumbering. Compartment scope per workload (cross-link: oci-iam-07-compartment-hierarchy) is the structural fix.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the workload VCN with an explicit CIDR in the workload compartment.
oci network vcn create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --cidr-blocks '["10.40.0.0/16"]' \
  --display-name vcn-app-prod \
  --dns-label appprod

# Step 2: create the private workload subnet (no public IPs allowed on any VNIC).
oci network subnet create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --cidr-block 10.40.10.0/24 \
  --display-name snet-app-prod-private \
  --prohibit-public-ip-on-vnic true \
  --dns-label appprodpriv

# Step 3: create the data-tier subnet (no Internet Gateway route).
oci network subnet create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --cidr-block 10.40.20.0/24 \
  --display-name snet-app-prod-data \
  --prohibit-public-ip-on-vnic true \
  --dns-label appproddata

# Step 4: create the regional DRG for hub-and-spoke and attach the workload VCN.
oci network drg create \
  --compartment-id "$NETWORK_COMPARTMENT_OCID" \
  --display-name drg-hub-eu-frankfurt-1

oci network drg-attachment create \
  --drg-id "$DRG_OCID" \
  --vcn-id "$VCN_OCID" \
  --display-name attach-vcn-app-prod

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_core_vcn" "app_prod" {
  compartment_id = var.workload_compartment_id
  cidr_blocks    = ["10.40.0.0/16"]
  display_name   = "vcn-app-prod"
  dns_label      = "appprod"
}

resource "oci_core_subnet" "app_prod_private" {
  compartment_id              = var.workload_compartment_id
  vcn_id                      = oci_core_vcn.app_prod.id
  cidr_block                  = "10.40.10.0/24"
  display_name                = "snet-app-prod-private"
  prohibit_public_ip_on_vnic  = true
  dns_label                   = "appprodpriv"
}

resource "oci_core_subnet" "app_prod_data" {
  compartment_id              = var.workload_compartment_id
  vcn_id                      = oci_core_vcn.app_prod.id
  cidr_block                  = "10.40.20.0/24"
  display_name                = "snet-app-prod-data"
  prohibit_public_ip_on_vnic  = true
  dns_label                   = "appproddata"
}

# Regional DRG (hub) — attach many workload VCNs and FastConnect / IPsec here.
resource "oci_core_drg" "hub" {
  compartment_id = var.network_compartment_id
  display_name   = "drg-hub-eu-frankfurt-1"
}

resource "oci_core_drg_attachment" "vcn_app_prod" {
  drg_id       = oci_core_drg.hub.id
  vcn_id       = oci_core_vcn.app_prod.id
  display_name = "attach-vcn-app-prod"
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-01-vcn-design" \
  --display-name "oci-net-01-vcn-design" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-01-vcn-design" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a2.x (verify) SC-7; CM-2A.8.20; A.8.22CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'core' with eventName in (CreateVcn, UpdateVcn, CreateInternetGateway) inside a compartment normally reserved for private workloads.
  • Route-table mutation events (UpdateRouteTable) that introduce an Internet Gateway target on a route previously carrying NAT or Service-Gateway egress only.
  • VCN CIDR overlap with the corporate on-prem allocation as detected by a periodic diff of the VCN inventory against the IPAM source of truth.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'core'
          and eventName in ('CreateVcn', 'CreateInternetGateway', 'UpdateRouteTable')
          | eval added_igw = if(eventName = 'UpdateRouteTable' and data.request.payload.routeRules like '%internetGateway%', 'YES', 'NO')
          | where eventName in ('CreateVcn', 'CreateInternetGateway') or added_igw = 'YES'
          | stats count by 'User Name', 'Compartment Name', eventName

Tag every compartment with NetworkClass = private|edge so the saved search can narrow to private-class compartments where an IGW should never appear.

Alert threshold

  • Any new Internet Gateway in a NetworkClass = private compartment — page on first event.
  • Any VCN whose CIDR overlaps the IPAM allocation table — page; CIDR overlap breaks downstream peering and DNS resolution.

Initial response

  1. Detach the Internet Gateway from affected route tables via Resource Manager; OCI applies the route change online without flow disruption to NAT/SGW traffic.
  2. If the VCN itself is the deviation, plan a controlled VCN replacement with the correct CIDR and a documented migration window; do not edit CIDR in place (the operation is unsupported).
  3. Brief the network team and capture the topology change per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-02-sl-nsg-no-admin ! CRITICAL PREVENTIVE

No Security List and no Network Security Group in any compartment of the tenancy may permit ingress from 0.0.0.0/0 on administrative ports — SSH 22, RDP 3389, Oracle Database 1521, PostgreSQL 5432, MySQL 3306, SQL Server 1433, MongoDB 27017, Redis 6379, and any other database or management port the organization uses. Both surfaces must enforce the same invariant because OCI evaluates Security Lists and NSGs as a union: a packet is allowed if either surface admits it, so leaving the deny on only one surface is insufficient (Oracle Cloud Infrastructure — Security Rules (Security Lists and NSGs) (accessed 2026-05)). Anti-conflation: Security Lists are subnet-scoped — every VNIC in the subnet inherits the rules. NSGs are VNIC-scoped and can reference other NSGs as source or destination, which is how OCI expresses "tier-A may reach tier-B" without enumerating CIDRs (the security-group-style surface). Use NSGs for per-tier and per-role grouping logic; use Security Lists for blanket subnet-wide defaults; both must deny admin-port ingress from the public internet because the union-evaluation model gives neither surface a veto. Access path: operators reach instances via the OCI Bastion service (per-session time-limited SSH or managed port-forwarding to instances with no public IP, audited via the OCI Audit service); putting a public IP on a workload instance and opening TCP 22 to 0.0.0.0/0 is the canonical OCI ops anti-pattern, not an alternative to weigh. CRITICAL because this is the "open the internet to my database" misconfiguration; Shodan-style scanners locate exposures within minutes, and CIS OCI v2.0.0 §2.1 and §2.2 codify the requirement for both surfaces. The principle traces back to General Network — zero trust: never trust source-IP filtering as the only control on a management port.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: replace the default Security List ingress rules with an empty array (no ingress).
# Egress rules are kept; admin ingress invariant lives on the NSG side too.
oci network security-list update \
  --security-list-id "$DEFAULT_SL_OCID" \
  --ingress-security-rules '[]' \
  --force

# Step 2: create the workload NSG and attach to VNICs via instance/LB configuration.
oci network nsg create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --display-name nsg-app-tier

# Step 3: add an explicit allow-from-bastion-NSG rule on SSH (NSGs can reference other NSGs).
oci network nsg rules add \
  --nsg-id "$APP_NSG_OCID" \
  --security-rules '[{
    "direction": "INGRESS",
    "protocol": "6",
    "source": "'"$BASTION_NSG_OCID"'",
    "sourceType": "NETWORK_SECURITY_GROUP",
    "tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}},
    "description": "SSH only from Bastion NSG; no 0.0.0.0/0 ingress"
  }]'

# Step 4: audit the tenancy for any Security List or NSG rule allowing 0.0.0.0/0 on admin ports.
oci search resource structured-search \
  --query-text "query SecurityList resources where (freeformTags.audit = 'admin-port-review')" \
  --output table

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
# Default Security List with an EMPTY ingress array — admin invariant enforced by absence of rules.
resource "oci_core_default_security_list" "vcn_default" {
  manage_default_resource_id = oci_core_vcn.app_prod.default_security_list_id

  # Egress: permit all (egress controls covered in oci-net-08).
  egress_security_rules {
    destination = "0.0.0.0/0"
    protocol    = "all"
  }

  # Ingress: deliberately empty. No rule => no admit on this surface.
}

# Workload NSG — VNIC-scoped, can reference other NSGs as source.
resource "oci_core_network_security_group" "app_tier" {
  compartment_id = var.workload_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "nsg-app-tier"
}

resource "oci_core_network_security_group" "bastion_tier" {
  compartment_id = var.workload_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "nsg-bastion-tier"
}

# SSH allowed ONLY from the bastion NSG — no 0.0.0.0/0 source possible.
resource "oci_core_network_security_group_security_rule" "ssh_from_bastion_nsg" {
  network_security_group_id = oci_core_network_security_group.app_tier.id
  direction                 = "INGRESS"
  protocol                  = "6" # TCP
  source                    = oci_core_network_security_group.bastion_tier.id
  source_type               = "NETWORK_SECURITY_GROUP"

  tcp_options {
    destination_port_range {
      min = 22
      max = 22
    }
  }

  description = "SSH only from Bastion NSG; 0.0.0.0/0 ingress on admin ports is denied by absence"
}

# Explicit deny-by-absence: NO rule with source = "0.0.0.0/0" on admin ports anywhere.
# NSGs are allow-list; the deny is modelled by absence (documented in prose).

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-02-sl-nsg-no-admin" \
  --display-name "oci-net-02-sl-nsg-no-admin" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-02-sl-nsg-no-admin" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Remediation — Pulumi (TypeScript)

import * as pulumi from "@pulumi/pulumi";
import * as oci from "@pulumi/oci";

// NSG with NO admin-ingress from internet (SSH:22, RDP:3389 from 0.0.0.0/0 banned).
const cfg = new pulumi.Config();
const compartmentId = cfg.require("compartmentOcid");
const vcnOcid = cfg.require("vcnOcid");
const bastionCidr = cfg.require("bastionCidr");  // e.g., 10.0.99.0/24

const appNsg = new oci.core.NetworkSecurityGroup("app-tier-nsg", {
  compartmentId: compartmentId,
  vcnId: vcnOcid,
  displayName: "app-tier-no-admin-internet",
});

// SSH ingress: ONLY from bastion CIDR — never 0.0.0.0/0.
const sshFromBastion = new oci.core.NetworkSecurityGroupSecurityRule("ssh-bastion-only", {
  networkSecurityGroupId: appNsg.id,
  direction: "INGRESS",
  protocol: "6",   // TCP
  source: bastionCidr,
  sourceType: "CIDR_BLOCK",
  tcpOptions: { destinationPortRange: { min: 22, max: 22 } },
  description: "SSH from bastion subnet only — explicit allow",
});

// RDP: not exposed at all on Linux app tier; if Windows tier exists, mirror the SSH pattern.
// 0.0.0.0/0 ingress on any port to this NSG is rejected by Cloud Guard policy upstream.

export const nsgOcid = appNsg.id;

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a2.1; 2.2 SC-7(5); SC-7A.8.20; A.8.22CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' with eventName in (UpdateSecurityList, UpdateNetworkSecurityGroupSecurityRules) whose ingress rule payload contains the literal source CIDR 0.0.0.0/0 against destination ports 22 or 3389.
  • NSG attachment events binding a public-internet-exposed NSG to a Compute instance in a workload compartment normally protected by Bastion-mediated access.
  • VCN Flow Logs successful inbound flows to port 22 or 3389 from non-corporate source ASNs.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'core'
          and eventName in ('UpdateSecurityList', 'UpdateNetworkSecurityGroupSecurityRules')
          | eval body = data.request.payload
          | where body like '%"source":"0.0.0.0/0"%' and (body like '%"destinationPortRange":{"min":22%' or body like '%"destinationPortRange":{"min":3389%')
          | stats count by 'User Name', data.target.id, 'Compartment Name'

Security-list and NSG bodies are flat JSON; substring matching is sufficient for the canonical world-open-admin-port pattern.

Alert threshold

  • Any rule introducing 0.0.0.0/0 on TCP 22 or 3389 — page on first event; admin access from the open internet is a control-fence break.
  • Inbound flow record on TCP 22 or 3389 from a non-corporate ASN — page; correlate with the rule history to identify the originating mutation.

Initial response

  1. Revert the security list or NSG rules via Resource Manager to the last-known-good HCL; OCI accepts the change online and the new rule set takes effect immediately.
  2. Sample VCN Flow Logs across the exposure window for any matching inbound 22/3389 sessions; capture session 5-tuples for forensic export.
  3. If session data shows successful TCP handshakes, treat affected hosts as potentially compromised — snapshot the boot volume, isolate via NSG deny-all, and escalate per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-03-private-endpoint ! HIGH PREVENTIVE

Front OCI managed-service instances — Autonomous Database, Object Storage (where supported), OKE control planes, FSS file systems, and other services that expose a Private Endpoint integration — with a Private Endpoint VNIC inside a private subnet of the workload VCN. The Private Endpoint allocates a Customer-VNIC inside a subnet you control, with a 10.x private IP that consuming workloads connect to instead of the service's public endpoint; the underlying managed-service instance has no exposed public surface in this configuration (Oracle Cloud Infrastructure — Private Endpoints documentation (accessed 2026-05)). The principle is reinforced in General Network — private connectivity: managed-service traffic that never traverses a public endpoint cannot be intercepted at a public endpoint, and the blast radius of a leaked tenancy admin credential does not include "exfiltrate from any IP on the internet". Anti-conflation with Service Gateway (oci-net-04): Private Endpoint is per-service-instance and creates an explicit private VNIC inside your subnet; Service Gateway is bulk regional routing to the Oracle Services Network and does not put a VNIC in your subnet. Choose PE when the workload needs CIDR-level reachability of a specific managed-service instance (for example, "this app talks to this ADB only"); choose SG when the workload needs broad reachability of the Oracle Services Network (for example, Object Storage from many subnets). Most production VCNs use both. Compartment-policy scope (oci-iam-08-policy-least-privilege) keeps the right groups able to use the Private Endpoint without giving them the ability to recreate it on a public address.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: provision the Autonomous Database with a Private Endpoint into a private subnet.
oci db autonomous-database create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --db-name appprod \
  --display-name adb-app-prod \
  --cpu-core-count 2 \
  --data-storage-size-in-tbs 1 \
  --admin-password "$ADB_ADMIN_PW" \
  --subnet-id "$PRIVATE_SUBNET_OCID" \
  --private-endpoint-label adb-app-prod-pe \
  --nsg-ids '["'"$APP_NSG_OCID"'"]' \
  --is-mtls-connection-required true

# Step 2: create a generic Private Endpoint for an OCI service inside the private subnet.
oci network private-endpoint create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --subnet-id "$PRIVATE_SUBNET_OCID" \
  --display-name pe-managed-svc \
  --nsg-ids '["'"$APP_NSG_OCID"'"]'

# Step 3: confirm no public endpoint exists on the ADB.
oci db autonomous-database get \
  --autonomous-database-id "$ADB_OCID" \
  --query 'data."private-endpoint"'

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_database_autonomous_database" "app_prod" {
  compartment_id           = var.workload_compartment_id
  db_name                  = "appprod"
  display_name             = "adb-app-prod"
  cpu_core_count           = 2
  data_storage_size_in_tbs = 1
  admin_password           = var.adb_admin_password

  # Private Endpoint placement — no public endpoint.
  subnet_id               = oci_core_subnet.app_prod_private.id
  private_endpoint_label  = "adb-app-prod-pe"
  nsg_ids                 = [oci_core_network_security_group.app_tier.id]

  is_mtls_connection_required = true
}

# Generic Private Endpoint for an OCI managed-service instance inside the private subnet.
resource "oci_dns_view" "private_pe_view" {
  compartment_id = var.workload_compartment_id
  display_name   = "private-pe-view"
  scope          = "PRIVATE"
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-03-private-endpoint" \
  --display-name "oci-net-03-private-endpoint" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-03-private-endpoint" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a2.x (verify) SC-7; AC-4A.8.20; A.8.22CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' with eventName in (DeletePrivateEndpoint, UpdatePrivateEndpoint) targeting any service private endpoint serving Autonomous Database, Vault, or Object Storage.
  • Service-resource events that switch a managed service's network mode from PRIVATE to PUBLIC — surfaced as service-specific update events bearing a network-config attribute.
  • Private DNS zone deletions that severed the FQDN binding the workload uses to address the private endpoint.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' in ('core', 'database', 'object-storage', 'kms')
          and eventName in ('DeletePrivateEndpoint', 'UpdatePrivateEndpoint', 'UpdateAutonomousDatabase', 'UpdateVault')
          | eval net_mode = data.request.payload.networkAccessType
          | where eventName like 'PrivateEndpoint%' or net_mode = 'PUBLIC'
          | stats count by 'User Name', data.target.id, eventName, net_mode

Cross-service correlation is required because the private-endpoint surface is partly on core (the PE resource) and partly on individual service resources (the network-mode attribute).

Alert threshold

  • Any PrivateEndpoint delete or any service flipping networkAccessType to PUBLIC — page on first event.
  • Private DNS zone deletion against a zone hosting endpoint FQDNs — page; the workload's reachability collapses on next DNS TTL expiry.

Initial response

  1. Re-create the PrivateEndpoint via Resource Manager; the new endpoint may receive a different IP, so update the dependent DNS records and refresh consumer connection strings.
  2. Flip the service back to PRIVATE network mode via the service-specific update endpoint; downtime depends on the service (Autonomous DB rolls online; some services require restart).
  3. Verify VCN Flow Logs show traffic resumed to the new private IP and document the rebuild per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-04-service-gateway ! HIGH PREVENTIVE

Attach a Service Gateway to every workload VCN that needs to reach the Oracle Services Network — Object Storage, Autonomous Database control plane, OKE control plane, Container Registry, and the other OCI-managed services published in the regional Services Network — and route the matching service CIDR via the Service Gateway, not via a NAT Gateway or Internet Gateway. The Service Gateway forwards traffic over Oracle's backbone with no NAT and no exposure of the workload subnet to the public internet (Oracle Cloud Infrastructure — Service Gateway documentation (accessed 2026-05)). The route-table entry uses the symbolic destination all-<region>-services-in-oracle-services-network (or the narrower oci-<region>-object-storage for Object-Storage-only traffic) with the SGW as the network_entity_id. The principle is reinforced in General Network — egress control: traffic that does not need to traverse the public internet should not, both for confidentiality and for the egress-cost story. Anti-conflation with Private Endpoint (oci-net-03): Service Gateway is bulk regional routing — one SGW serves every workload in the VCN that needs to talk to the Oracle Services Network. Private Endpoint is per-managed-service-instance — one PE per ADB, one PE per FSS, and so on, each creating a VNIC in your subnet. SG does not put a VNIC in your subnet; PE does. Most production VCNs need both: PE for the specific managed-service instances the workload owns, SG for the broad "this VCN talks to Object Storage and ADB control planes" case.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: discover the Services Network destination OCIDs for the current region.
oci network service list --query 'data[*].{name:name, id:id}' --output table

# Step 2: create the Service Gateway and bind it to the workload VCN.
oci network service-gateway create \
  --compartment-id "$NETWORK_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --display-name sgw-app-prod \
  --services '[{"service_id":"'"$OCI_ALL_SERVICES_OCID"'"}]'

# Step 3: update the private subnet route table to send Services-Network traffic via the SGW.
oci network route-table update \
  --rt-id "$PRIVATE_RT_OCID" \
  --route-rules '[{
    "destination": "'"$OCI_ALL_SERVICES_CIDR_LABEL"'",
    "destinationType": "SERVICE_CIDR_BLOCK",
    "networkEntityId": "'"$SGW_OCID"'",
    "description": "Services Network via SGW; no public NAT"
  }]' \
  --force

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
data "oci_core_services" "all" {
  filter {
    name   = "name"
    values = ["All .* Services In Oracle Services Network"]
    regex  = true
  }
}

resource "oci_core_service_gateway" "app_prod" {
  compartment_id = var.network_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "sgw-app-prod"

  services {
    service_id = data.oci_core_services.all.services[0].id
  }
}

# Route private subnet's Services-Network traffic through the SGW (not via NAT).
resource "oci_core_route_table" "app_prod_private" {
  compartment_id = var.network_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "rt-app-prod-private"

  route_rules {
    destination       = data.oci_core_services.all.services[0].cidr_block
    destination_type  = "SERVICE_CIDR_BLOCK"
    network_entity_id = oci_core_service_gateway.app_prod.id
    description       = "Services Network via SGW; no public NAT"
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-04-service-gateway" \
  --display-name "oci-net-04-service-gateway" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-04-service-gateway" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a2.x (verify) SC-7(8); AC-4A.8.20; A.8.22CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'core' with eventName in (DeleteServiceGateway, UpdateServiceGateway) whose payload narrows the services binding (e.g. removes OCI All Services in Region).
  • Route-table updates that remove the Service Gateway target from a subnet's outbound rule, forcing OCI-service-bound traffic to traverse a NAT Gateway and emit through the public internet path.
  • VCN Flow Logs showing previously-Service-Gateway-bound destinations now appearing on the NAT Gateway egress flow.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'core'
          and eventName in ('DeleteServiceGateway', 'UpdateServiceGateway', 'UpdateRouteTable')
          | eval rule_drop = if(eventName = 'UpdateRouteTable' and not data.request.payload.routeRules like '%serviceGateway%', 'POSSIBLE', 'NO')
          | where eventName like 'ServiceGateway%' or rule_drop = 'POSSIBLE'
          | stats count by 'User Name', data.target.id, eventName

Service Gateway changes are infrequent; bind the saved search to the SGW OCID inventory tag EgressClass = oci-services.

Alert threshold

  • Any DeleteServiceGateway on a tenancy-managed SGW — page; OCI-bound traffic falls back to the NAT path and accrues egress charges plus public-internet exposure.
  • Route-table update removing the SGW target on a subnet flagged as EgressClass = oci-services — page.

Initial response

  1. Re-create the Service Gateway via Resource Manager and restore the subnet route binding; OCI applies the route update online.
  2. Audit VCN Flow Logs across the gap window for any OCI-service traffic that traversed the NAT Gateway and brief the FinOps team on the egress-charge impact.
  3. Re-attach the SGW's services binding to the documented service-list scope and document per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-05-waf ! HIGH PREVENTIVE

Attach an OCI Web Application Firewall policy to every public Flexible Load Balancer or Network Load Balancer that fronts a workload exposed to the internet. The WAF policy carries OWASP CRS-based protection capabilities (SQL injection, cross-site scripting, local and remote file inclusion, remote-code-execution heuristics, protocol-anomaly detection, and scanner detection), bot-management features, and L7 access controls — and it is the only WAF surface in OCI, replacing the legacy Edge WAF (Oracle Cloud Infrastructure — Web Application Firewall overview (accessed 2026-05)). Protection capabilities are referenced by their stable numeric IDs (for example, 920420 for request-content-type enforcement, 941100 for XSS, 942100 for SQLi) rather than full rule-name strings, which Oracle treats as documentation rather than API contract. The principle is reinforced in General Network — defense in depth: L7 inspection at the LB edge catches payload-shaped attacks that L4 firewalls (Security Lists, NSGs) cannot see, and the two layers are complementary rather than alternative. Anti-conflation with platform DDoS (oci-net-06): the WAF policy and the platform DDoS layer live on different planes — WAF inspects HTTP payloads after the platform has already absorbed the L3/L4 flood; both are on by default once the WAF policy is attached.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the WAF policy with OWASP CRS protection rules and rate limiting.
oci waf web-app-firewall-policy create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name waf-app-prod \
  --request-protection '{
    "rules": [{
      "type": "PROTECTION",
      "name": "owasp-crs-protect",
      "actionName": "block",
      "protectionCapabilities": [
        {"key": "920420", "version": 1},
        {"key": "941100", "version": 1},
        {"key": "942100", "version": 1},
        {"key": "933100", "version": 1}
      ]
    }]
  }' \
  --actions '[{"name": "block", "type": "RETURN_HTTP_RESPONSE", "code": 403}]'

# Step 2: attach the policy to the public Flexible Load Balancer.
oci waf web-app-firewall create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name wafe-app-prod \
  --web-app-firewall-policy-id "$WAF_POLICY_OCID" \
  --backend-type LOAD_BALANCER \
  --load-balancer-id "$PUBLIC_LB_OCID"

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_waf_web_app_firewall_policy" "app_prod" {
  compartment_id = var.workload_compartment_id
  display_name   = "waf-app-prod"

  actions {
    name = "block403"
    type = "RETURN_HTTP_RESPONSE"
    code = 403
  }

  request_protection {
    rules {
      type         = "PROTECTION"
      name         = "owasp-crs-protect"
      action_name  = "block403"

      protection_capabilities { key = "920420" version = 1 } # request-content-type
      protection_capabilities { key = "941100" version = 1 } # XSS
      protection_capabilities { key = "942100" version = 1 } # SQLi
      protection_capabilities { key = "933100" version = 1 } # PHP injection
    }
  }
}

resource "oci_waf_web_app_firewall" "app_prod" {
  compartment_id              = var.workload_compartment_id
  display_name                = "wafe-app-prod"
  web_app_firewall_policy_id  = oci_waf_web_app_firewall_policy.app_prod.id
  backend_type                = "LOAD_BALANCER"
  load_balancer_id            = oci_load_balancer_load_balancer.public.id
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-05-waf" \
  --display-name "oci-net-05-waf" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-05-waf" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a(best-practices) SC-7(11); SI-3A.8.20; A.8.23CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'waf' with eventName in (UpdateWebAppFirewallPolicy, DeleteWebAppFirewall) whose payload removes managed protection rules or moves policy mode to OFF.
  • WAF access-log records ('Log Source' = 'OCI Web Application Firewall Logs') showing a drop in BLOCK action counts greater than 80% week-over-week.
  • WAF protection-rule policy version diffs that drop the OWASP Core Rule Set or any tenant-custom rule covering an application-specific exploit signature.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'waf'
          and eventName in ('UpdateWebAppFirewallPolicy', 'DeleteWebAppFirewall')
          | eval action = data.request.payload.actions
          | where action like '%"action":"ALLOW"%' or eventName = 'DeleteWebAppFirewall'
          | stats count by 'User Name', data.target.webAppFirewall.id, eventName

WAF policy payloads are large JSON; the gate fires on rule-set removal patterns and on policy-mode transitions to permit-all.

Alert threshold

  • Any WAF policy update removing the OWASP CRS — page; this is the application-tier baseline.
  • WAF BLOCK action count drop greater than 80% week-over-week on a production-tagged policy — page; correlate with rule diffs to confirm a tuning change vs an availability incident.

Initial response

  1. Re-apply the WAF policy via Resource Manager to restore the OWASP CRS and any tenant-custom rules; OCI WAF accepts policy updates online and propagates within seconds.
  2. Review WAF access logs across the weakened window for any traffic that would have been blocked under the prior rule set; export to Object Storage for forensic retention.
  3. Brief the application security team on the policy delta and update the WAF dashboard per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-06-ddos ! MEDIUM RESPONSIVE

OCI Layer 3/4 DDoS protection is always-on at the platform edge, free, and not separately enabled — every public IP attached to a Flexible Load Balancer, Network Load Balancer, or compute VNIC is covered by the platform's volumetric-flood absorption layer at no charge and with no configuration knobs (Oracle Cloud Infrastructure — DDoS protection overview (accessed 2026-05)). The operator-visible control surface is Layer 7: rate-limiting and connection-limit rules on the WAF policy (oci-net-05) and on the Load Balancer's listener and backend-set configuration, which constrain how aggressively a single client IP or a single TLS session can consume backend capacity. Severity MEDIUM RESPONSIVE because the platform layer is not operator-configurable (so the control is in the response loop — sizing limits, tightening rate caps during a sustained attack, engaging Oracle Support for an active mitigation review) rather than in a pre-incident preventive posture. The principle is reinforced in General Network — defense in depth: L3/L4 absorption and L7 rate limiting protect different parts of the request lifecycle and are complementary. Anti-conflation with the WAF rule plane (oci-net-05): request-protection rules (OWASP CRS) and request-rate-limiting rules live on the same WAF policy resource but address different attack shapes — protection rules block payload-shaped attacks regardless of volume; rate-limit rules block volume regardless of payload shape. Both are typically configured together.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: add L7 rate limiting to the existing WAF policy (global 1000 req/min/IP).
oci waf web-app-firewall-policy update \
  --web-app-firewall-policy-id "$WAF_POLICY_OCID" \
  --request-rate-limiting '{
    "rules": [{
      "type": "REQUEST_RATE_LIMITING",
      "name": "global-rate-limit",
      "actionName": "block403",
      "configurations": [{
        "periodInSeconds": 60,
        "requestsLimit": 1000,
        "actionDurationInSeconds": 600
      }]
    }]
  }' \
  --force

# Step 2: confirm Load Balancer backend connection limit is sized to absorb spikes.
oci lb backend-set update \
  --load-balancer-id "$PUBLIC_LB_OCID" \
  --backend-set-name app-backend \
  --policy LEAST_CONNECTIONS \
  --health-checker '{"protocol":"HTTP","port":8080,"urlPath":"/health"}' \
  --force

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
# Extend the WAF policy resource (oci-net-05) with a request-rate-limiting block.
resource "oci_waf_web_app_firewall_policy" "app_prod_with_rate_limit" {
  compartment_id = var.workload_compartment_id
  display_name   = "waf-app-prod"

  actions {
    name = "block403"
    type = "RETURN_HTTP_RESPONSE"
    code = 403
  }

  # Same request_protection block as oci-net-05 (omitted for brevity).

  request_rate_limiting {
    rules {
      type        = "REQUEST_RATE_LIMITING"
      name        = "global-rate-limit"
      action_name = "block403"

      configurations {
        period_in_seconds          = 60
        requests_limit             = 1000
        action_duration_in_seconds = 600
      }
    }
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-06-ddos" \
  --display-name "oci-net-06-ddos" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-06-ddos" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/an/a (post-v2.0.0) SC-5; SC-5(2)A.8.20; A.5.30CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'waf' with eventName in (UpdateWebAppFirewallPolicy) whose payload removes rate-limiting rules at the edge.
  • Load Balancer or Network Load Balancer connection-rate metrics indicating a sustained surge (more than 10× the 7-day p99) without a corresponding application-team traffic-event ticket.
  • OCI DDoS Protection threshold breach events emitted as Cloud Guard problems of detector type OciDdosVolumetricBreach.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' in ('waf', 'load-balancer')
          and eventName in ('UpdateWebAppFirewallPolicy', 'UpdateLoadBalancer', 'UpdateNetworkLoadBalancer')
          | eval rl_dropped = if(data.request.payload.actions like '%"requestRateLimiting":null%', 'YES', 'NO')
          | where rl_dropped = 'YES' or eventName like '%LoadBalancer'
          | stats count by 'User Name', data.target.id, eventName

Pair the audit-side gate with an OCI Monitoring alarm on Load Balancer connection-rate to catch a runtime surge even when the policy change is benign.

Alert threshold

  • Any WAF policy update removing rate-limiting rules — page; the edge tier loses the volumetric brake.
  • Load Balancer connection rate greater than 10× the 7-day p99 without an active traffic-event ticket — page.

Initial response

  1. Re-attach the rate-limiting rule via Resource Manager; OCI WAF applies the rule within seconds and immediately starts shedding excess load.
  2. If a live surge is in progress, escalate to OCI support to engage tenant-tier DDoS Protection assistance and increase OCI Monitoring sample frequency for the affected Load Balancer.
  3. Capture the source-IP distribution from WAF access logs across the surge window for upstream blocking via the Load Balancer's path-route firewall.

References

Equivalent on: AWS · Azure · GCP

oci-net-07-dns ! MEDIUM PREVENTIVE

Enable DNSSEC on every public DNS zone hosted on OCI DNS, and route private resolution through private DNS views bound to the VCN so workload hostnames never appear in public resolvers. DNSSEC adds origin authentication to DNS responses via KSK / ZSK chains rooted at the parent zone's DS records; once configured, recursive resolvers that perform validation reject responses with tampered or absent signatures, eliminating off-path DNS spoofing as an avenue for credential-phishing or service-impersonation attacks against the organization's domain (Oracle Cloud Infrastructure — DNSSEC documentation (accessed 2026-05)). KSK / ZSK rotation can be automated on a schedule; DS-record handoff to the parent zone is the only operator step that crosses an organisational boundary and must be tracked in change-management. The principle is reinforced in General Network — DNS security. Private DNS views (oci_dns_view) plus a Resolver attached to the VCN replace the legacy "Internet and VCN Resolver" with explicit per-VCN private zones, so internal hostnames resolve only inside the VCN and are not leakable through public resolvers.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the public DNS zone.
oci dns zone create \
  --compartment-id "$NETWORK_COMPARTMENT_OCID" \
  --name example.com \
  --zone-type PRIMARY \
  --scope GLOBAL

# Step 2: enable DNSSEC on the zone (DNSSEC enablement step per region; consult realm docs).
oci dns zone update \
  --zone-name-or-id "$ZONE_OCID" \
  --is-protected true \
  --scope GLOBAL \
  --force

# Step 3: retrieve DS records for parent-zone handoff.
oci dns zone get \
  --zone-name-or-id "$ZONE_OCID" \
  --scope GLOBAL \
  --query 'data."dnssec-config"."ksk-dnssec-key-versions"'

# Step 4: create the private DNS view + resolver for VCN-internal resolution.
oci dns view create \
  --compartment-id "$NETWORK_COMPARTMENT_OCID" \
  --display-name view-vcn-app-prod-internal \
  --scope PRIVATE

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_dns_zone" "public_example_com" {
  compartment_id = var.network_compartment_id
  name           = "example.com"
  zone_type      = "PRIMARY"
  scope          = "GLOBAL"

  # DNSSEC enablement — DS record handoff to parent zone is the explicit operator step.
  dnssec_state = "ENABLED"
}

# Private DNS view bound to the workload VCN — internal hostnames resolve only inside the VCN.
resource "oci_dns_view" "private_internal" {
  compartment_id = var.network_compartment_id
  display_name   = "view-vcn-app-prod-internal"
  scope          = "PRIVATE"
}

resource "oci_dns_resolver" "vcn_app_prod" {
  resolver_id = oci_core_vcn.app_prod.default_dhcp_options_id
  scope       = "PRIVATE"
  display_name = "resolver-vcn-app-prod"

  attached_views {
    view_id = oci_dns_view.private_internal.id
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-07-dns" \
  --display-name "oci-net-07-dns" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-07-dns" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a2.x (verify) SC-20; SC-21A.8.21CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'dns' with eventName in (UpdateZone, DeleteZone, DisableDnssec) whose payload toggles isDnssecEnabled off.
  • Zone-delegation update events (UpdateZoneRecords) altering NS records on a parent zone — these change the chain of trust irrespective of DNSSEC state.
  • OCI DNS resolver-log records showing a sustained drop in DNSSEC AD-flag responses on the tenancy-managed zones.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'dns'
          and eventName in ('UpdateZone', 'DeleteZone', 'DisableDnssec', 'UpdateZoneRecords')
          | eval dnssec_off = if(data.request.payload.isDnssecEnabled = 'false', 'YES', 'NO')
          | where dnssec_off = 'YES' or eventName in ('DeleteZone', 'DisableDnssec', 'UpdateZoneRecords')
          | stats count by 'User Name', data.target.zone.name, eventName

DNS mutations are infrequent and ticketed; non-zero counts outside the change-management window are anomalous.

Alert threshold

  • Any DisableDnssec or DNSSEC toggle off on a zone listed in the tenancy's apex-zone inventory — page on first event.
  • NS-record mutation on a zone where the NS set previously pointed to OCI nameservers — page; chain-of-trust changes warrant explicit ticketing.

Initial response

  1. Re-enable DNSSEC on the zone via Resource Manager; OCI DNS regenerates the DS records and the parent-zone publishes within propagation latency.
  2. If NS records were altered, restore the prior NS set from zone-file version history and notify the registrar of the parent zone if a delegation change was published upstream.
  3. Verify dig +dnssec from a known-good resolver returns the AD flag for the zone apex and document the rollback per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-net-08-nat-gateway ! HIGH PREVENTIVE

Route outbound public traffic from private subnets through a NAT Gateway — never by assigning public IPs to workload instances — and pair the NAT Gateway with a Network Firewall (OCI's managed Palo Alto VM-Series offering) when the workload's egress needs FQDN-aware or L7-aware filtering beyond what a Security List or NSG egress rule can express. The NAT Gateway is stateful SNAT with no inbound surface; the workload subnet's route table directs 0.0.0.0/0 through the NAT, and Service-Network traffic continues to use the Service Gateway (oci-net-04) so backbone traffic does not consume NAT capacity (Oracle Cloud Infrastructure — NAT Gateway documentation (accessed 2026-05)). Egress NSG and Security List rules at L4 close the obvious holes (deny 0.0.0.0/0 egress on database client ports from non-DB tiers, scope per-port allowlists to specific destinations where possible); the Network Firewall closes the L7 / FQDN hole when the workload needs to reach a small set of named external services but cannot enumerate their IPs. The principle is reinforced in General Network — egress control: outbound traffic is the data-exfiltration surface that NAT-Gateway-as-the-only-egress-control leaves wide open. Anti-conflation: NAT Gateway = SNAT with no inspection — every TCP/UDP destination is permitted at the NAT layer itself. Network Firewall (oci_network_firewall_*) = L7 inspection and FQDN allowlisting on a managed Palo Alto policy. Egress NSG rules = stateful L4 destination policy. Use all three planes together; do not treat NAT as "egress control" by itself.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the NAT Gateway in the network compartment.
oci network nat-gateway create \
  --compartment-id "$NETWORK_COMPARTMENT_OCID" \
  --vcn-id "$VCN_OCID" \
  --display-name natgw-app-prod \
  --block-traffic false

# Step 2: route 0.0.0.0/0 from the private subnet via NAT (preserve SGW for Services Network).
oci network route-table update \
  --rt-id "$PRIVATE_RT_OCID" \
  --route-rules '[
    {
      "destination": "0.0.0.0/0",
      "destinationType": "CIDR_BLOCK",
      "networkEntityId": "'"$NATGW_OCID"'",
      "description": "Default egress via NAT Gateway"
    },
    {
      "destination": "'"$OCI_ALL_SERVICES_CIDR_LABEL"'",
      "destinationType": "SERVICE_CIDR_BLOCK",
      "networkEntityId": "'"$SGW_OCID"'",
      "description": "Services Network via SGW; not via NAT"
    }
  ]' \
  --force

# Step 3: deny egress on database client ports from the app tier NSG (defence in depth).
oci network nsg rules add \
  --nsg-id "$APP_NSG_OCID" \
  --security-rules '[{
    "direction": "EGRESS",
    "protocol": "6",
    "destination": "0.0.0.0/0",
    "destinationType": "CIDR_BLOCK",
    "tcpOptions": {"destinationPortRange": {"min": 1521, "max": 1521}},
    "isStateless": false,
    "description": "Egress Oracle DB to public denied — DB lives on Private Endpoint"
  }]'

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_core_nat_gateway" "app_prod" {
  compartment_id = var.network_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "natgw-app-prod"
  block_traffic  = false
}

# Combined route table: 0.0.0.0/0 via NAT, Services Network via SGW.
resource "oci_core_route_table" "app_prod_private_egress" {
  compartment_id = var.network_compartment_id
  vcn_id         = oci_core_vcn.app_prod.id
  display_name   = "rt-app-prod-private-egress"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_nat_gateway.app_prod.id
    description       = "Default egress via NAT Gateway"
  }

  route_rules {
    destination       = data.oci_core_services.all.services[0].cidr_block
    destination_type  = "SERVICE_CIDR_BLOCK"
    network_entity_id = oci_core_service_gateway.app_prod.id
    description       = "Services Network via SGW; not via NAT"
  }
}

# Network Firewall policy for L7 / FQDN egress filtering (managed Palo Alto VM-Series).
resource "oci_network_firewall_network_firewall_policy" "egress_l7" {
  compartment_id = var.network_compartment_id
  display_name   = "nfwp-app-prod-egress"
}

resource "oci_network_firewall_network_firewall" "egress_l7" {
  compartment_id              = var.network_compartment_id
  display_name                = "nfw-app-prod-egress"
  network_firewall_policy_id  = oci_network_firewall_network_firewall_policy.egress_l7.id
  subnet_id                   = oci_core_subnet.app_prod_private.id
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-net-08-nat-gateway" \
  --display-name "oci-net-08-nat-gateway" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-net-08-nat-gateway" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a(best-practices) SC-7(5); AC-4A.8.20; A.8.22CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'core' with eventName in (CreateInternetGateway, UpdateRouteTable) whose payload swaps a NAT Gateway target for an Internet Gateway on a private-subnet route.
  • Subnet-attribute mutations (UpdateSubnet) flipping prohibitPublicIpOnVnic from true to false — workloads in the subnet can now self-assign public IPs.
  • VCN Flow Logs showing inbound traffic from internet sources to subnets previously bound to NAT-only egress.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'core'
          and eventName in ('CreateInternetGateway', 'UpdateRouteTable', 'UpdateSubnet')
          | eval public_ip_allowed = if(eventName = 'UpdateSubnet' and data.request.payload.prohibitPublicIpOnVnic = 'false', 'YES', 'NO')
          | eval igw_added = if(eventName = 'UpdateRouteTable' and data.request.payload.routeRules like '%internetGateway%', 'YES', 'NO')
          | where public_ip_allowed = 'YES' or igw_added = 'YES' or eventName = 'CreateInternetGateway'
          | stats count by 'User Name', data.target.id, eventName

Tag every subnet with EgressMode = nat|igw so the saved search can suppress alerts on subnets architecturally designed for IGW egress.

Alert threshold

  • Any subnet flagged EgressMode = nat moving to IGW-bound routing — page on first event.
  • Any prohibitPublicIpOnVnic = false flip on a private subnet — page; workloads in the subnet can now reach the public internet without traversing the central NAT.

Initial response

  1. Restore the NAT-only route via Resource Manager and detach the Internet Gateway from the affected subnets.
  2. Re-set prohibit_public_ip_on_vnic = true on the subnet and audit any VNIC carrying a public IP since the flip — strip the public IP via oci network vnic update.
  3. Brief the network team and capture the topology drift per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

Sources