OCI Compute Instances Should Not Have Public IP Addresses
More Info:
Compute instances should not have public IP addresses assigned. Public IPs expose instances directly to internet-based attacks and should be avoided in favor of load balancers or bastion hosts.
Risk Level
Critical
Address
Compliance, Security
Compliance Standards
- APRA CPS 234 (Australia)
- AWS Startup Security Baseline
- AWS Well Architected Framework
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- FedRAMP
- HIPAA
- HITRUST CSF
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST CSF
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- PCI
- Reserve Bank of India (RBI) Cyber Security Framework
- SOC2
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Using Console
Below are step‑by‑step console instructions to remediate “OCI Compute Instances Should Not Have Public IP Addresses.”
1. Identify Instances With Public IPs
- Sign in to OCI Console.
- Choose the correct Region (top right).
- Open the navigation menu → Compute → Instances.
- Select the Compartment where your instances reside.
- For each instance:
- Click the instance name.
- Under Resources (left side), click Attached VNICs.
- Click the primary VNIC.
- In the VNIC details, check:
- Public IP field
- If present, note the Public IP address and whether it’s ephemeral or reserved.
You will remediate each instance/VNIC that has a Public IP.
2. Remove Public IP From the Instance (VNIC)
You cannot detach a primary VNIC, but you can remove or change its Public IP.
For safety, stop the instance if it’s production and you want to avoid connection issues during change.
2.1 (Optional but recommended) Stop the Instance
- On the instance details page, click Stop.
- Wait until the state is Stopped.
2.2 Remove or Change the Public IP
- On the same instance page, under Resources, click Attached VNICs.
- Click the primary VNIC (usually named something like
instance-name (Primary VNIC)). - In the VNIC details page:
- Locate the IPv4 Addresses section.
- Find the Primary private IP row.
- In the Public IP column:
- Click the Actions menu (three dots) or the link for the public IP.
Depending on the type:
If it’s an Ephemeral Public IP:
- In the Public IP details, click Edit or Actions → Unassign / Remove.
- Confirm the unassignment.
- After this, the Primary private IP will show No public IP address.
If it’s a Reserved Public IP:
- In the Public IP details, click Edit or Actions → Unassign.
- Confirm.
- (Optional cleanup) If you no longer need that reserved IP:
- Go to navigation menu → Networking → Public IPs.
- Choose the correct Compartment.
- Find that Reserved Public IP, click its name.
- Click Release Public IP to return it to the pool.
2.3 Start the Instance (if you stopped it)
- Return to the instance page.
- Click Start.
- Confirm that applications are reachable via private paths (VPN, FastConnect, Bastion, etc.).
3. Prevent Future Public IP Assignment at Subnet Level
To avoid new instances in that subnet getting public IPs:
- Open navigation menu → Networking → Virtual Cloud Networks.
- Select the VCN where the instance’s subnet resides.
- Under Resources, click Subnets.
- Click the subnet that was used by your instance.
- Click Edit.
- Set:
- Public IP address:
- Uncheck or choose option that disables public IP assignment (wording may be “Do not assign public IPv4 addresses” or similar).
- Public IP address:
- Click Save changes.
New instances or VNICs in this subnet will not be allowed to have public IPs.
4. (Optional) Use OCI Bastion for Private-Only Access
If you used the public IP for SSH/RDP:
- Open navigation menu → Identity & Security → Bastion.
- Create a Bastion in:
- The same Region.
- A public subnet with proper security list/NSG rules.
- Configure a Session (SSH, RDP, etc.) from your workstation through the Bastion to the private IP of your instance.
- Confirm you can connect without needing a public IP on the instance.
5. Confirm Remediation for Monitoring/Compliance
To ensure OCI monitoring/compliance tools recognize the remediation:
- Wait for the next evaluation cycle (usually a few minutes up to an hour, depending on the security/monitoring service).
- In your OCI Security/Monitoring or Cloud Guard dashboards, confirm that:
- The problem “Compute Instances Should Not Have Public IP Addresses” for those instances is either:
- Marked Resolved, or
- No longer shown as active.
- The problem “Compute Instances Should Not Have Public IP Addresses” for those instances is either:
You have now removed public IP addresses from your OCI Compute instances and hardened the subnet to prevent new public IPs from being assigned.
Using CLI
Below is a focused, step‑by‑step way to remediate “OCI Compute Instances Should Not Have Public IP Addresses” using the OCI CLI. The core actions are:
- Identify instances with public IPs
- Remove public IPs from their VNICs
- Prevent new public IP assignment at subnet level
1. Prerequisites
- OCI CLI installed and configured (
oci setup config) - You know:
- Your
compartment-ocid - The region you’re working in (
--regionif different from default)
- Your
2. Identify Compute Instances with Public IPs
2.1. List instances in a compartment
oci compute instance list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"lifecycle-state\"=='RUNNING' || \"lifecycle-state\"=='STOPPED'].{id:id,\"display-name\":\"display-name\"}" \
--output table
For each instance, get its VNICs and public IPs.
2.2. For each instance, get its VNIC attachments
INSTANCE_OCID=<instance-ocid>
oci compute vnic-attachment list \
--compartment-id <compartment-ocid> \
--instance-id $INSTANCE_OCID \
--all \
--query "data[].{\"id\":id,\"vnic-id\":\"vnic-id\"}" \
--output table
2.3. For each VNIC, check if it has a public IP
VNIC_ID=<vnic-ocid>
oci network vnic get \
--vnic-id $VNIC_ID \
--query "data.{\"vnic-id\":id,\"public-ip\":\"public-ip\",\"is-primary\":\"is-primary\"}" \
--output table
If public-ip is not null, that VNIC currently has a public IP.
3. Remove Public IP Addresses from VNICs
There are two types of public IPs: ephemeral and reserved. You unassign them differently.
3.1. Determine the public IP object (ephemeral vs reserved)
Use the VNIC’s public-ip from the previous step (IP address string).
IP_ADDRESS=<public-ip-address>
oci network public-ip list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"ip-address\"=='$IP_ADDRESS']"
If it returns a resource with an OCID and "lifetime": "RESERVED", it’s a reserved public IP.
If it doesn’t return anything, it’s likely an ephemeral public IP associated directly to the VNIC.
3.2. If the public IP is ephemeral (most common)
Use vnic update to remove the public IP:
oci network vnic update \
--vnic-id $VNIC_ID \
--assign-public-ip false
This detaches the ephemeral public IP from the VNIC.
3.3. If the public IP is reserved
First, unassign the reserved public IP from the VNIC, then optionally delete it.
3.3.1. Find the reserved public IP OCID
oci network public-ip list \
--compartment-id <compartment-ocid> \
--all \
--query "data[?\"ip-address\"=='$IP_ADDRESS'].id | [0]" \
--raw-output
Save it:
PUBLIC_IP_ID=<public-ip-ocid>
3.3.2. Unassign the reserved public IP from the VNIC
oci network public-ip update \
--public-ip-id $PUBLIC_IP_ID \
--private-ip-id "" \
--vnic-id ""
Some older API patterns require you to update the private-ip-id to null (or assign to a different private IP). If the above fails, you can:
- Get the private IP behind the VNIC:
oci network private-ip list \--vnic-id $VNIC_ID \--query "data[?\"is-primary\"==\`true\`].id | [0]" \--raw-output
- Use
oci network public-ip updateto dissociate or reassign as required (depends on tenancy policy and version). If removing directly is not allowed, you may need to reassign to some “holding” private IP that is not used by a compute instance.
3.3.3. Optionally delete the reserved public IP (to avoid reuse)
oci network public-ip delete \
--public-ip-id $PUBLIC_IP_ID \
--force
4. Prevent Future Public IP Assignment at Subnet Level
To ensure instances in a subnet cannot get public IPs:
4.1. Get subnet details and current setting
SUBNET_ID=<subnet-ocid>
oci network subnet get \
--subnet-id $SUBNET_ID \
--query "data.{id:id,\"display-name\":\"display-name\",\"prohibit-public-ip-on-vnic\":\"prohibit-public-ip-on-vnic\"}" \
--output table
If prohibit-public-ip-on-vnic is false, change it to true.
4.2. Update the subnet
oci network subnet update \
--subnet-id $SUBNET_ID \
--prohibit-public-ip-on-vnic true \
--force
From now on, new VNICs in this subnet cannot have public IPs. Existing VNICs with public IPs are not automatically stripped; you must remediate them as in steps 2–3.
5. Bulk / Scripted Remediation Pattern (Optional)
To remediate all instances in a compartment:
-
List all VNICs that currently have a
public-ip:oci compute instance list --compartment-id <compartment-ocid> --all \| jq -r '.data[].id' \| while read INSTANCE_ID; dooci compute vnic-attachment list --compartment-id <compartment-ocid> --instance-id "$INSTANCE_ID" --all \| jq -r '.data[]."vnic-id"' \| while read VNIC_ID; dooci network vnic get --vnic-id "$VNIC_ID" \| jq -r '.data | select(.["public-ip"] != null) | ."public-ip"' \| while read IP; doecho "Removing public IP $IP from VNIC $VNIC_ID (instance $INSTANCE_ID)"oci network vnic update --vnic-id "$VNIC_ID" --assign-public-ip falsedonedonedone -
Then update subnets as in section 4 to prevent re‑introduction.
If you share your compartment OCID / subnet structure (sanitized), I can help format exact ready‑to‑run commands for your layout.
Using Python
Below is a concise, Python‑based approach to detect and remediate OCI Compute instances that have public IPs by unassigning those IPs from their VNICs.
Assumptions:
- You have
ociPython SDK installed (pip install oci). - Your
~/.oci/configis set up, or you otherwise pass config programmatically. - You understand that unassigning public IPs may break connectivity (SSH/RDP) to those instances.
1. Install and Import OCI SDK
pip install oci
import oci
from oci.core import ComputeClient, VirtualNetworkClient
2. Create OCI Clients
config = oci.config.from_file("~/.oci/config", "DEFAULT")
compute_client = ComputeClient(config)
vcn_client = VirtualNetworkClient(config)
compartment_id = "<your_compartment_ocid>"
3. List Instances and Their VNICs
# Get all instances in the compartment
instances = oci.pagination.list_call_get_all_results(
compute_client.list_instances,
compartment_id=compartment_id,
lifecycle_state="RUNNING" # or omit to include all
).data
Now get each instance’s VNIC attachments:
vnic_attachments = []
for instance in instances:
vas = oci.pagination.list_call_get_all_results(
compute_client.list_vnic_attachments,
compartment_id=compartment_id,
instance_id=instance.id
).data
vnic_attachments.extend(vas)
4. Detect VNICs with Public IPs
vnics_with_public_ip = []
for va in vnic_attachments:
vnic = vcn_client.get_vnic(va.vnic_id).data
if vnic.public_ip:
vnics_with_public_ip.append((vnic, va))
At this point you have all VNICs with public IPs.
5. Unassign the Public IPs
There are two cases in OCI:
- Ephemeral Public IPs (assigned directly to VNIC).
- Reserved Public IPs (separate OCI resource, associated with a private IP).
5.1. Unassign Ephemeral Public IP
for vnic, va in vnics_with_public_ip:
# For ephemeral public IPs, the public IP is directly tied to the VNIC primary private IP.
# Get the private IP resource:
private_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_private_ips,
vnic_id=vnic.id
).data
for priv_ip in private_ips:
# if this private IP has an associated public IP (ephemeral)
if priv_ip.public_ip:
print(f"Unassigning ephemeral public IP from VNIC {vnic.id} (Instance: {va.instance_id})")
# Update private IP to remove public IP
update_details = oci.core.models.UpdatePrivateIpDetails(
defined_tags=priv_ip.defined_tags,
display_name=priv_ip.display_name,
freeform_tags=priv_ip.freeform_tags,
hostname_label=priv_ip.hostname_label
# No explicit field to “unset” public_ip; we remove via UpdatePublicIp or delete reserved IP
)
# In practice, ephemeral IPs are deleted by deleting/ resetting the association:
# For ephemeral public IPs, you usually call:
# vcn_client.update_public_ip(...) only for reserved; for ephemeral, you use DeletePublicIp when exposed.
# OCI exposes ephemeral public IPs via get_public_ip_by_ip_address for deletion.
public_ip_obj = vcn_client.get_public_ip_by_ip_address(
get_public_ip_by_ip_address_details=oci.core.models.GetPublicIpByIpAddressDetails(
ip_address=priv_ip.public_ip
)
).data
# Delete (unassign) the public IP
vcn_client.delete_public_ip(public_ip_obj.id)
Note: For ephemeral public IPs, they are modeled as a
PublicIpresource of typeEPHEMERAL. Deleting thatPublicIpunassigns it from the private IP.
5.2. Unassign Reserved Public IPs
Reserved public IPs may be attached to specific private IPs. Detection is similar; identify the PublicIp resource and delete or “unassign” it.
# List all reserved public IPs in compartment, then detach them
public_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_public_ips,
compartment_id=compartment_id,
scope="REGION"
).data
for pub_ip in public_ips:
if pub_ip.lifecycle_state != "ASSIGNED":
continue
# If you want to remove all instance public IPs:
if pub_ip.assigned_entity_id: # usually the private IP OCID
print(f"Unassigning reserved public IP {pub_ip.ip_address} ({pub_ip.id})")
# Delete reserved public IP – this unassigns it
vcn_client.delete_public_ip(pub_ip.id)
6. Wrap into a “Remediation Script”
Combine the logic into a script that:
- Lists instances and their VNICs.
- Finds all VNICS/private IPs with public IPs.
- Resolves the corresponding
PublicIpresources. - Deletes (unassigns) those
PublicIpresources.
Skeleton:
import oci
def remove_public_ips_from_instances(compartment_id, config_profile="DEFAULT"):
config = oci.config.from_file("~/.oci/config", config_profile)
compute_client = oci.core.ComputeClient(config)
vcn_client = oci.core.VirtualNetworkClient(config)
instances = oci.pagination.list_call_get_all_results(
compute_client.list_instances,
compartment_id=compartment_id,
lifecycle_state="RUNNING"
).data
for instance in instances:
vas = oci.pagination.list_call_get_all_results(
compute_client.list_vnic_attachments,
compartment_id=compartment_id,
instance_id=instance.id
).data
for va in vas:
vnic = vcn_client.get_vnic(va.vnic_id).data
if not vnic.public_ip:
continue
private_ips = oci.pagination.list_call_get_all_results(
vcn_client.list_private_ips,
vnic_id=vnic.id
).data
for priv_ip in private_ips:
if not priv_ip.public_ip:
continue
# Get the public IP resource
public_ip_obj = vcn_client.get_public_ip_by_ip_address(
get_public_ip_by_ip_address_details=oci.core.models.GetPublicIpByIpAddressDetails(
ip_address=priv_ip.public_ip
)
).data
print(f"Unassigning public IP {public_ip_obj.ip_address} from instance {instance.display_name} ({instance.id})")
vcn_client.delete_public_ip(public_ip_obj.id)
if __name__ == "__main__":
remove_public_ips_from_instances("<your_compartment_ocid>")
7. (Optional) Monitoring / Alerting
To integrate this with “compute monitoring”:
- Use OCI Monitoring / Alarms or Cloud Guard to detect instances with public IPs (e.g., via IAM/Config rules or Cloud Guard detector).
- Configure an Alarm or Cloud Guard Responder that triggers a Notification (OCI Events + Functions or OCI Notifications to a function).
- That Function can run the Python logic above (packaged into a function) to automatically remediate.
If you specify whether you want a standalone script, OCI Function code, or integration with Cloud Guard/Events, I can give a tailored Python example for that environment.
Using Terraform
resource "oci_core_instance" "COMPUTE_INSTANCE_NAME" {
# Replace with your compartment and availability details
compartment_id = "OCID_OF_COMPARTMENT"
availability_domain = "AVAILABILITY_DOMAIN_NAME"
shape = "VM.SHAPE_NAME"
display_name = "COMPUTE_INSTANCE_NAME"
source_details {
source_type = "image"
source_id = "OCID_OF_IMAGE"
}
create_vnic_details {
subnet_id = "OCID_OF_SUBNET"
hostname_label = "HOSTNAME_LABEL"
assign_public_ip = "false" # Ensure no public IP is assigned
}
# ... any other required arguments ...
}
Setting assign_public_ip = "false" (or omitting it when the subnet is private-by-default) prevents the instance from receiving a public IP; changing this on an existing instance forces replacement (destroy and recreate) of the instance/VNIC in Terraform.
After the change, terraform plan should show the assign_public_ip argument changing from "true" (or "yes") to "false" with a planned -/+ replacement for the oci_core_instance resource.